Skip to content

Commit

Permalink
add AWS IAM Authentication strategy (#61)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkrauser authored Nov 22, 2023
1 parent 535aeea commit 5380167
Show file tree
Hide file tree
Showing 4 changed files with 255 additions and 1 deletion.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"psr/cache": "^1.0|^2.0|^3.0",
"psr/log": "^1.0|^2.0|^3.0",
"psr/http-client": "^1.0",
"psr/http-factory": "^1.0"
"psr/http-factory": "^1.0",
"aws/aws-sdk-php": "^3.0"
},
"require-dev": {
"codeception/codeception": "^4.1",
Expand Down
120 changes: 120 additions & 0 deletions src/AuthenticationStrategies/AwsIamAuthenticationStrategy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

namespace Vault\AuthenticationStrategies;

use Aws\Middleware;
use Aws\Sts\StsClient;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Client\ClientExceptionInterface;
use Vault\Exceptions\AuthenticationException;
use Vault\ResponseModels\Auth;

/**
* Class AwsIamAuthenticationStrategy
*
* @package Vault\AuthenticationStrategy
*/
class AwsIamAuthenticationStrategy extends AbstractAuthenticationStrategy
{
/**
* @var string
*/
protected $methodPathSegment = 'aws';

/** @var string */
private $role;

/** @var string */
private $region;

/** @var ?string */
private $serverId;

/**
* @param string $role The name of the vault role
* @param string $region The AWS region to use
* @param ?string $vaultServerId If set, this string is used as X-Vault-AWS-IAM-Server-ID, to protect against replay attacks
* @param ?StsClient $stsClient Custom instance of StsClient
*/
public function __construct(
$role,
$region,
$vaultServerId = null,
$stsClient = null
) {
$this->role = $role;
$this->region = $region;
$this->serverId = $vaultServerId;
$this->stsClient = $stsClient;
}

/**
* Returns auth for further interactions with Vault.
*
* @return Auth
* @throws AuthenticationException
* @throws ClientExceptionInterface
*/
public function authenticate(): Auth
{
if (!$this->methodPathSegment) {
throw new AuthenticationException('methodPathSegment must be set before usage');
}

if (!$this->stsClient) {
$this->stsClient = new StsClient([
'region' => $this->region,
'version' => 'latest',
'sts_regional_endpoints' => 'regional',
]);
}


// Creating a signed command, to get the parameters for the actual login-request to vault
$command = $this->stsClient->getCommand('GetCallerIdentity');

if ($this->serverId) {
$command->getHandlerList()->appendBuild(
Middleware::mapRequest(function (RequestInterface $request) {
return $request->withHeader('X-Vault-AWS-IAM-Server-ID', $this->serverId);
}),
'add-header'
);
}

$request = \Aws\serialize($command);

$response = $this->client->write(
sprintf('/auth/%s/login', $this->methodPathSegment),
[
'role' => $this->role,
'iam_http_request_method' => $request->getMethod(),
'iam_request_url' => base64_encode($request->getUri()),
'iam_request_body' => base64_encode($request->getBody()),
'iam_request_headers' => base64_encode(json_encode($request->getHeaders())),
]
);

return $response->getAuth();
}

/**
* @return string
*/
public function getMethodPathSegment(): string
{
return $this->methodPathSegment;
}

/**
* @param string $methodPathSegment
*
* @return static
*/
public function setMethodPathSegment(string $methodPathSegment)
{
$this->methodPathSegment = $methodPathSegment;

return $this;
}
}
46 changes: 46 additions & 0 deletions tests/_data/vcr/authentication-strategies/aws-iam
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@

-
request:
method: POST
url: 'http://127.0.0.1:8200/v1/auth/aws/login'
headers:
Host: '127.0.0.1:8200'
Expect: null
Accept-Encoding: null
User-Agent: VaultPHP/1.0.0
Content-Type: application/json
Accept: null
body: '{"role":"dev-role","iam_http_request_method":"POST","iam_request_url":"aHR0cHM6Ly9zdHMuZXUtY2VudHJhbC0xLmFtYXpvbmF3cy5jb20=","iam_request_body":"QWN0aW9uPUdldENhbGxlcklkZW50aXR5JlZlcnNpb249MjAxMS0wNi0xNQ==","iam_request_headers":"eyJIb3N0IjpbInN0cy5ldS1jZW50cmFsLTEuYW1hem9uYXdzLmNvbSJdLCJDb250ZW50LUxlbmd0aCI6WyI0MyJdLCJDb250ZW50LVR5cGUiOlsiYXBwbGljYXRpb25cL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCJdLCJYLUFtei1Vc2VyLUFnZW50IjpbImF3cy1zZGstcGhwXC8zLjI2MC4zIE9TXC9EYXJ3aW5cLzIyLjEuMCBsYW5nXC9waHBcLzguMC4yNyJdLCJVc2VyLUFnZW50IjpbImF3cy1zZGstcGhwXC8zLjI2MC4zIE9TXC9EYXJ3aW5cLzIyLjEuMCBsYW5nXC9waHBcLzguMC4yNyJdLCJYLVZhdWx0LUFXUy1JQU0tU2VydmVyLUlEIjpbImxvY2FsaG9zdCJdLCJhd3Mtc2RrLXJldHJ5IjpbIjBcLzAiXX0="}'
response:
status:
http_version: '1.1'
code: '200'
message: OK
headers:
Cache-Control: no-store
Content-Type: application/json
Date: 'Tue, 01 Aug 2017 06:54:33 GMT'
Content-Length: '366'
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"
-
request:
method: GET
url: 'http://127.0.0.1:8200/v1/auth/token/lookup-self'
headers:
Host: '127.0.0.1:8200'
Accept-Encoding: null
User-Agent: VaultPHP/1.0.0
Content-Type: application/json
X-Vault-Token: 9e64c3b8-01f7-7a64-1575-30a91f7d1ae4
Accept: null
response:
status:
http_version: '1.1'
code: '200'
message: OK
headers:
Cache-Control: no-store
Content-Type: application/json
Date: 'Tue, 01 Aug 2017 06:54:33 GMT'
Content-Length: '504'
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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?php

use Aws\Sts\StsClient;
use Codeception\Test\Unit;
use Psr\Http\Client\ClientExceptionInterface;
use Vault\AuthenticationStrategies\AwsIamAuthenticationStrategy;
use Vault\Client;
use VCR\VCR;
use Laminas\Diactoros\RequestFactory;
use Laminas\Diactoros\StreamFactory;
use Laminas\Diactoros\Uri;

class AwsIamAuthenticationStrategyTest extends Unit
{
/**
* @var UnitTester
*/
protected $tester;

/**
* @throws \Psr\Cache\InvalidArgumentException
* @throws ClientExceptionInterface
* @throws \Vault\Exceptions\RuntimeException
*/
public function testCanAuthenticate(): void
{
$client = new Client(
new Uri('http://127.0.0.1:8200'),
new \AlexTartan\GuzzlePsr18Adapter\Client(),
new RequestFactory(),
new StreamFactory()
);

$stsClient = new StsClient([
'region' => 'eu-central-1',
'version' => 'latest',
'sts_regional_endpoints' => 'regional'
]);
// These middlewares would break the test, due to some dynamic headers
$stsClient->getHandlerList()->remove('invocation-id');
$stsClient->getHandlerList()->remove('signer');

$client->setAuthenticationStrategy(
new AwsIamAuthenticationStrategy(
'dev-role',
'eu-central-1',
'localhost',
$stsClient
)
);

$this->assertEquals($client->getAuthenticationStrategy()->getClient(), $client);
$this->assertTrue($client->authenticate());
$this->assertNotEmpty($client->getToken());
$this->assertNotEmpty($client->getToken()->getAuth()->getLeaseDuration());
$this->assertNotEmpty($client->getToken()->getAuth()->isRenewable());
}

protected function setUp(): void
{
VCR::turnOn();
VCR::configure()->setMode(VCR::MODE_ONCE);

VCR::insertCassette('authentication-strategies/aws-iam');

parent::setUp();
}

protected function tearDown(): void
{
// To stop recording requests, eject the cassette
VCR::eject();

// Turn off VCR to stop intercepting requests
VCR::turnOff();

parent::tearDown();
}

protected function _before()
{
}

protected function _after()
{
}
}

0 comments on commit 5380167

Please sign in to comment.