Skip to content

Commit

Permalink
feat: add service account identity support to GCECredentials (#304)
Browse files Browse the repository at this point in the history
  • Loading branch information
bshaffer authored Sep 15, 2020
1 parent 74cad28 commit 142d1d7
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 15 deletions.
84 changes: 70 additions & 14 deletions src/Credentials/GCECredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -159,16 +159,28 @@ class GCECredentials extends CredentialsLoader implements
*/
private $quotaProject;

/**
* @var string|null
*/
private $serviceAccountIdentity;

/**
* @param Iam $iam [optional] An IAM instance.
* @param string|array $scope [optional] the scope of the access request,
* expressed either as an array or as a space-delimited string.
* @param string $targetAudience [optional] The audience for the ID token.
* @param string $quotaProject [optional] Specifies a project to bill for access
* charges associated with the request.
* @param string $serviceAccountIdentity [optional] Specify a service
* account identity name to use instead of "default".
*/
public function __construct(Iam $iam = null, $scope = null, $targetAudience = null, $quotaProject = null)
{
public function __construct(
Iam $iam = null,
$scope = null,
$targetAudience = null,
$quotaProject = null,
$serviceAccountIdentity = null
) {
$this->iam = $iam;

if ($scope && $targetAudience) {
Expand All @@ -177,7 +189,7 @@ public function __construct(Iam $iam = null, $scope = null, $targetAudience = nu
);
}

$tokenUri = self::getTokenUri();
$tokenUri = self::getTokenUri($serviceAccountIdentity);
if ($scope) {
if (is_string($scope)) {
$scope = explode(' ', $scope);
Expand All @@ -187,41 +199,82 @@ public function __construct(Iam $iam = null, $scope = null, $targetAudience = nu

$tokenUri = $tokenUri . '?scopes='. $scope;
} elseif ($targetAudience) {
$tokenUri = sprintf(
'http://%s/computeMetadata/%s?audience=%s',
self::METADATA_IP,
self::ID_TOKEN_URI_PATH,
$targetAudience
);
$tokenUri = self::getIdTokenUri($serviceAccountIdentity);
$tokenUri = $tokenUri . '?audience='. $targetAudience;
$this->targetAudience = $targetAudience;
}

$this->tokenUri = $tokenUri;
$this->quotaProject = $quotaProject;
$this->serviceAccountIdentity = $serviceAccountIdentity;
}

/**
* The full uri for accessing the default token.
*
* @param string $serviceAccountIdentity [optional] Specify a service
* account identity name to use instead of "default".
* @return string
*/
public static function getTokenUri()
public static function getTokenUri($serviceAccountIdentity = null)
{
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';
$base .= self::TOKEN_URI_PATH;

return $base . self::TOKEN_URI_PATH;
if ($serviceAccountIdentity) {
return str_replace(
'/default/',
'/' . $serviceAccountIdentity . '/',
$base
);
}
return $base;
}

/**
* The full uri for accessing the default service account.
*
* @param string $serviceAccountIdentity [optional] Specify a service
* account identity name to use instead of "default".
* @return string
*/
public static function getClientNameUri($serviceAccountIdentity = null)
{
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';
$base .= self::CLIENT_ID_URI_PATH;

if ($serviceAccountIdentity) {
return str_replace(
'/default/',
'/' . $serviceAccountIdentity . '/',
$base
);
}

return $base;
}

/**
* The full uri for accesesing the default identity token.
*
* @param string $serviceAccountIdentity [optional] Specify a service
* account identity name to use instead of "default".
* @return string
*/
public static function getClientNameUri()
private static function getIdTokenUri($serviceAccountIdentity = null)
{
$base = 'http://' . self::METADATA_IP . '/computeMetadata/';
$base .= self::ID_TOKEN_URI_PATH;

if ($serviceAccountIdentity) {
return str_replace(
'/default/',
'/' . $serviceAccountIdentity . '/',
$base
);
}

return $base . self::CLIENT_ID_URI_PATH;
return $base;
}

/**
Expand Down Expand Up @@ -388,7 +441,10 @@ public function getClientName(callable $httpHandler = null)
return '';
}

$this->clientName = $this->getFromMetadata($httpHandler, self::getClientNameUri());
$this->clientName = $this->getFromMetadata(
$httpHandler,
self::getClientNameUri($this->serviceAccountIdentity)
);

return $this->clientName;
}
Expand Down
93 changes: 92 additions & 1 deletion tests/Credentials/GCECredentialsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,99 @@ public function testGetProjectIdShouldBeEmptyIfNotOnGCE()

HttpClientCache::setHttpClient($client->reveal());


$creds = new GCECredentials;
$this->assertNull($creds->getProjectId());
}

public function testGetTokenUriWithServiceAccountIdentity()
{
$tokenUri = GCECredentials::getTokenUri('foo');
$this->assertEquals(
'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/foo/token',
$tokenUri
);
}

public function testGetAccessTokenWithServiceAccountIdentity()
{
$expected = [
'access_token' => 'token12345',
'expires_in' => 123,
];
$timesCalled = 0;
$httpHandler = function ($request) use (&$timesCalled, $expected) {
$timesCalled++;
if ($timesCalled == 1) {
return new Psr7\Response(200, [GCECredentials::FLAVOR_HEADER => 'Google']);
}
$this->assertEquals(
'/computeMetadata/v1/instance/service-accounts/foo/token',
$request->getUri()->getPath()
);
$this->assertEquals('', $request->getUri()->getQuery());
return new Psr7\Response(200, [], Psr7\stream_for(json_encode($expected)));
};

$g = new GCECredentials(null, null, null, null, 'foo');
$this->assertEquals(
$expected['access_token'],
$g->fetchAuthToken($httpHandler)['access_token']
);
}

public function testGetIdTokenWithServiceAccountIdentity()
{
$expected = 'idtoken12345';
$timesCalled = 0;
$httpHandler = function ($request) use (&$timesCalled, $expected) {
$timesCalled++;
if ($timesCalled == 1) {
return new Psr7\Response(200, [GCECredentials::FLAVOR_HEADER => 'Google']);
}
$this->assertEquals(
'/computeMetadata/v1/instance/service-accounts/foo/identity',
$request->getUri()->getPath()
);
$this->assertEquals(
'audience=a+target+audience',
$request->getUri()->getQuery()
);
return new Psr7\Response(200, [], Psr7\stream_for($expected));
};
$g = new GCECredentials(null, null, 'a+target+audience', null, 'foo');
$this->assertEquals(
['id_token' => $expected],
$g->fetchAuthToken($httpHandler)
);
}

public function testGetClientNameUriWithServiceAccountIdentity()
{
$clientNameUri = GCECredentials::getClientNameUri('foo');
$this->assertEquals(
'http://169.254.169.254/computeMetadata/v1/instance/service-accounts/foo/email',
$clientNameUri
);
}

public function testGetClientNameWithServiceAccountIdentity()
{
$expected = 'expected';
$timesCalled = 0;
$httpHandler = function ($request) use (&$timesCalled, $expected) {
$timesCalled++;
if ($timesCalled == 1) {
return new Psr7\Response(200, [GCECredentials::FLAVOR_HEADER => 'Google']);
}
$this->assertEquals(
'/computeMetadata/v1/instance/service-accounts/foo/email',
$request->getUri()->getPath()
);
$this->assertEquals('', $request->getUri()->getQuery());
return new Psr7\Response(200, [], Psr7\stream_for($expected));
};

$creds = new GCECredentials(null, null, null, null, 'foo');
$this->assertEquals($expected, $creds->getClientName($httpHandler));
}
}

0 comments on commit 142d1d7

Please sign in to comment.