Skip to content

Commit

Permalink
add metadata server imds v2 token
Browse files Browse the repository at this point in the history
  • Loading branch information
peze committed May 9, 2024
1 parent 1d8383c commit 116148a
Show file tree
Hide file tree
Showing 9 changed files with 404 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/Credential/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ class Config

public $expiration = 0;

public $enableIMDSv2 = false;

public $metadataTokenDuration = 21600;

public function __construct($config)
{
foreach ($config as $k => $v) {
Expand Down
28 changes: 26 additions & 2 deletions src/EcsRamRoleCredential.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,35 @@ class EcsRamRoleCredential implements CredentialsInterface
*/
private $roleName;

/**
* @var boolean
*/
private $enableIMDSv2;

/**
* @var int
*/
private $metadataTokenDuration;


/**
* EcsRamRoleCredential constructor.
*
* @param $role_name
*/
public function __construct($role_name = null)
public function __construct($role_name = null, $enable_IMDS_v2 = false, $metadata_token_duration = 21600 )
{
Filter::roleName($role_name);

$this->roleName = $role_name;

Filter::enableIMDSv2($enable_IMDS_v2);

$this->enableIMDSv2 = $enable_IMDS_v2;

Filter::metadataTokenDuration($metadata_token_duration);

$this->metadataTokenDuration = $metadata_token_duration;
}

/**
Expand Down Expand Up @@ -116,7 +135,11 @@ public function getAccessKeyId()
*/
protected function getSessionCredential()
{
return (new EcsRamRoleProvider($this))->get();
$config = [
'enableIMDSv2' => $this->enableIMDSv2,
'metadataTokenDuration' => $this->metadataTokenDuration,
];
return (new EcsRamRoleProvider($this, $config))->get();
}

/**
Expand Down Expand Up @@ -148,4 +171,5 @@ public function getExpiration()
{
return $this->getSessionCredential()->getExpiration();
}

}
17 changes: 17 additions & 0 deletions src/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,23 @@ public static function roleName($role_name)
}
}

/**
* @param boolean|null $enable_IMDS_v2
*/
public static function enableIMDSv2($enable_IMDS_v2)
{
if (!is_bool($enable_IMDS_v2)) {
throw new InvalidArgumentException('enable_IMDS_v2 must be a string');
}
}


public static function metadataTokenDuration($metadata_token_duration) {
if (!is_int($metadata_token_duration)) {
throw new InvalidArgumentException('metadata_token_duration must be a int');
}
}

/**
* @param string $accessKeyId
* @param string $accessKeySecret
Expand Down
22 changes: 22 additions & 0 deletions src/MockTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Middleware;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;

Expand All @@ -21,6 +22,11 @@ trait MockTrait
*/
private static $mockQueue = [];

/**
* @var array
*/
private static $history = [];

/**
* @var MockHandler
*/
Expand All @@ -46,6 +52,14 @@ private static function createHandlerStack()
self::$mock = new MockHandler(self::$mockQueue);
}

/**
* @return MockHandler
*/
public static function getHandlerHistory()
{
return Middleware::history(self::$history);
}

/**
* @param string $message
* @param RequestInterface $request
Expand Down Expand Up @@ -95,4 +109,12 @@ public static function getMock()
{
return self::$mock;
}

/**
* @return array
*/
public static function getHistroy()
{
return self::$history;
}
}
93 changes: 90 additions & 3 deletions src/Providers/EcsRamRoleProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace AlibabaCloud\Credentials\Providers;

use AlibabaCloud\Credentials\Helper;
use AlibabaCloud\Credentials\Request\Request;
use AlibabaCloud\Credentials\StsCredential;
use Exception;
Expand All @@ -26,10 +27,32 @@ class EcsRamRoleProvider extends Provider
*/
protected $expirationSlot = 10;

/**
* refresh time for meta server token.
*
* @var int
*/
private $staleTime = 0;

/**
* @var string
*/
private $metadataHost = 'http://100.100.100.200';

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

/**
* @var string
*/
private $uri = 'http://100.100.100.200/latest/meta-data/ram/security-credentials/';
private $ecsUri = '/latest/meta-data/ram/security-credentials/';

/**
* @var string
*/
private $metadataTokenUri = '/latest/api/token';

/**
* Get credential.
Expand Down Expand Up @@ -59,6 +82,19 @@ public function get()
$result['SecurityToken']
);
}


protected function getEnableECSIMDSv2()
{
$enableIMDSv2 = Helper::envNotEmpty('ALIBABA_CLOUD_ECS_IMDSV2_ENABLE');
if ($enableIMDSv2) {
return strtolower($enableIMDSv2) === 'false' ? false : (bool)$enableIMDSv2;
}
if(isset($this->config['enableIMDSv2'])) {
return $this->config['enableIMDSv2'];
}
return false;
}

/**
* Get credentials by request.
Expand All @@ -70,13 +106,18 @@ public function get()
public function request()
{
$credential = $this->credential;
$url = $this->uri . $credential->getRoleName();
$url = $this->metadataHost . $this->ecsUri . $credential->getRoleName();

$options = [
'http_errors' => false,
'timeout' => 1,
'connect_timeout' => 1,
];

if ($this->getEnableECSIMDSv2()) {
$this->refreshMetadataToken();
$options['headers']['X-aliyun-ecs-metadata-token'] = $this->metadataToken;
}

$result = Request::createClient()->request('GET', $url, $options);

Expand All @@ -87,8 +128,54 @@ public function request()

if ($result->getStatusCode() !== 200) {
throw new RuntimeException('Error retrieving credentials from result: ' . $result->toJson());
}
}

return $result;
}

/**
* Get metadata token by request.
*
* @return ResponseInterface
* @throws Exception
* @throws GuzzleException
*/
protected function refreshMetadataToken()
{
if(!$this->needToRefresh()) {
return;
}
$credential = $this->credential;
$url = $this->metadataHost . $this->metadataTokenUri;
$tmpTime = $this->staleTime;
$this->staleTime = time() + $this->config['metadataTokenDuration'];
$options = [
'http_errors' => false,
'timeout' => 1,
'connect_timeout' => 1,
'headers' => [
'X-aliyun-ecs-metadata-token-ttl-seconds' => $this->config['metadataTokenDuration'],
],
];

$result = Request::createClient()->request('PUT', $url, $options);

if ($result->getStatusCode() != 200) {
$this->staleTime = $tmpTime;
throw new RuntimeException('Failed to get token from ECS Metadata Service. HttpCode= ' . $result->getStatusCode());
}

$this->metadataToken = $result->getBody();

return;
}


/**
* @return boolean
*/
protected function needToRefresh()
{
return \time() >= $this->staleTime;
}
}
2 changes: 2 additions & 0 deletions src/Request/Request.php
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@ public static function createClient()
{
if (Credentials::hasMock()) {
$stack = HandlerStack::create(Credentials::getMock());
$history = Credentials::getHandlerHistory();
$stack->push($history);
} else {
$stack = HandlerStack::create();
}
Expand Down
19 changes: 19 additions & 0 deletions tests/Unit/CredentialTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,25 @@ public function exceptionCases()
'role_name must be a string',
],

[
[
'type' => 'ecs_ram_role',
'role_name' => 'test',
'enableIMDSv2' => 'false',
],
'enable_IMDS_v2 must be a string',
],

[
[
'type' => 'ecs_ram_role',
'role_name' => 'test',
'enableIMDSv2' => false,
'metadataTokenDuration' => 3600,
],
'metadata_token_duration must be a int',
],

[
[
'type' => 'ram_role_arn',
Expand Down
28 changes: 28 additions & 0 deletions tests/Unit/EcsRamRoleCredentialTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
use InvalidArgumentException;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use ReflectionClass;

class EcsRamRoleCredentialTest extends TestCase
{
Expand Down Expand Up @@ -47,6 +48,33 @@ public function testConstruct()
$this->assertEquals($expected, (string)$credential);
}

private function getPrivateField($instance, $field) {
$reflection = new ReflectionClass(EcsRamRoleCredential::class);
$privateProperty = $reflection->getProperty($field);
$privateProperty->setAccessible(true);
return $privateProperty->getValue($instance);
}

/**
* @throws GuzzleException
*/
public function testConstructWithIMDSv2()
{
// Setup
$roleName = 'role_arn';
$enableIMDSv2 = true;
$metadataTokenDuration = 3600;
$credential = new EcsRamRoleCredential($roleName, $enableIMDSv2, $metadataTokenDuration);

self::assertEquals(true, $this->getPrivateField($credential, 'enableIMDSv2'));
self::assertEquals(3600, $this->getPrivateField($credential, 'metadataTokenDuration'));

$credential = new EcsRamRoleCredential($roleName);

self::assertEquals(false, $this->getPrivateField($credential, 'enableIMDSv2'));
self::assertEquals(21600, $this->getPrivateField($credential, 'metadataTokenDuration'));
}

/**
* @throws GuzzleException
*/
Expand Down
Loading

0 comments on commit 116148a

Please sign in to comment.