Skip to content

Commit

Permalink
feat: enable auth observability metrics (#509)
Browse files Browse the repository at this point in the history
  • Loading branch information
yash30201 committed May 2, 2024
1 parent 23b1fc1 commit 6495f31
Show file tree
Hide file tree
Showing 11 changed files with 505 additions and 15 deletions.
25 changes: 21 additions & 4 deletions src/Credentials/GCECredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ class GCECredentials extends CredentialsLoader implements
*/
private const GKE_PRODUCT_NAME_FILE = '/sys/class/dmi/id/product_name';

private const CRED_TYPE = 'mds';

/**
* Note: the explicit `timeout` and `tries` below is a workaround. The underlying
* issue is that resolving an unknown host on some networks will take
Expand Down Expand Up @@ -359,7 +361,10 @@ public static function onGce(callable $httpHandler = null)
new Request(
'GET',
$checkUri,
[self::FLAVOR_HEADER => 'Google']
[
self::FLAVOR_HEADER => 'Google',
self::$metricMetadataKey => self::getMetricsHeader('', 'mds')
]
),
['timeout' => self::COMPUTE_PING_CONNECTION_TIMEOUT_S]
);
Expand Down Expand Up @@ -421,7 +426,11 @@ public function fetchAuthToken(callable $httpHandler = null)
return []; // return an empty array with no access token
}

$response = $this->getFromMetadata($httpHandler, $this->tokenUri);
$response = $this->getFromMetadata(
$httpHandler,
$this->tokenUri,
$this->applyTokenEndpointMetrics([], $this->targetAudience ? 'it' : 'at')
);

if ($this->targetAudience) {
return $this->lastReceivedToken = ['id_token' => $response];
Expand Down Expand Up @@ -579,15 +588,18 @@ public function getUniverseDomain(callable $httpHandler = null): string
*
* @param callable $httpHandler An HTTP Handler to deliver PSR7 requests.
* @param string $uri The metadata URI.
* @param array<mixed> $headers [optional] If present, add these headers to the token
* endpoint request.
*
* @return string
*/
private function getFromMetadata(callable $httpHandler, $uri)
private function getFromMetadata(callable $httpHandler, $uri, array $headers = [])
{
$resp = $httpHandler(
new Request(
'GET',
$uri,
[self::FLAVOR_HEADER => 'Google']
[self::FLAVOR_HEADER => 'Google'] + $headers
)
);

Expand Down Expand Up @@ -619,4 +631,9 @@ public function setIsOnGce($isOnGce)
// Set isOnGce
$this->isOnGce = $isOnGce;
}

protected function getCredType(): string
{
return self::CRED_TYPE;
}
}
13 changes: 12 additions & 1 deletion src/Credentials/ImpersonatedServiceAccountCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ class ImpersonatedServiceAccountCredentials extends CredentialsLoader implements
{
use IamSignerTrait;

private const CRED_TYPE = 'imp';

/**
* @var string
*/
Expand Down Expand Up @@ -121,7 +123,11 @@ public function getClientName(callable $unusedHttpHandler = null)
*/
public function fetchAuthToken(callable $httpHandler = null)
{
return $this->sourceCredentials->fetchAuthToken($httpHandler);
// We don't support id token endpoint requests as of now for Impersonated Cred
return $this->sourceCredentials->fetchAuthToken(
$httpHandler,
$this->applyTokenEndpointMetrics([], 'at')
);
}

/**
Expand All @@ -139,4 +145,9 @@ public function getLastReceivedToken()
{
return $this->sourceCredentials->getLastReceivedToken();
}

protected function getCredType(): string
{
return self::CRED_TYPE;
}
}
16 changes: 15 additions & 1 deletion src/Credentials/ServiceAccountCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ class ServiceAccountCredentials extends CredentialsLoader implements
{
use ServiceAccountSignerTrait;

/**
* Used in observability metric headers
*
* @var string
*/
private const CRED_TYPE = 'sa';

/**
* The OAuth2 instance used to conduct authorization.
*
Expand Down Expand Up @@ -206,7 +213,9 @@ public function fetchAuthToken(callable $httpHandler = null)

return $accessToken;
}
return $this->auth->fetchAuthToken($httpHandler);
$authRequestType = empty($this->auth->getAdditionalClaims()['target_audience'])
? 'at' : 'it';
return $this->auth->fetchAuthToken($httpHandler, $this->applyTokenEndpointMetrics([], $authRequestType));
}

/**
Expand Down Expand Up @@ -344,6 +353,11 @@ public function getUniverseDomain(): string
return $this->universeDomain;
}

protected function getCredType(): string
{
return self::CRED_TYPE;
}

/**
* @return bool
*/
Expand Down
12 changes: 12 additions & 0 deletions src/Credentials/ServiceAccountJwtAccessCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ class ServiceAccountJwtAccessCredentials extends CredentialsLoader implements
{
use ServiceAccountSignerTrait;

/**
* Used in observability metric headers
*
* @var string
*/
private const CRED_TYPE = 'jwt';

/**
* The OAuth2 instance used to conduct authorization.
*
Expand Down Expand Up @@ -209,4 +216,9 @@ public function getQuotaProject()
{
return $this->quotaProject;
}

protected function getCredType(): string
{
return self::CRED_TYPE;
}
}
24 changes: 22 additions & 2 deletions src/Credentials/UserRefreshCredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@
*/
class UserRefreshCredentials extends CredentialsLoader implements GetQuotaProjectInterface
{
/**
* Used in observability metric headers
*
* @var string
*/
private const CRED_TYPE = 'u';

/**
* The OAuth2 instance used to conduct authorization.
*
Expand Down Expand Up @@ -98,6 +105,10 @@ public function __construct(

/**
* @param callable $httpHandler
* @param array<mixed> $metricsHeader [optional] Metrics headers to be inserted
* into the token endpoint request present.
* This could be passed from ImersonatedServiceAccountCredentials as it uses
* UserRefreshCredentials as source credentials.
*
* @return array<mixed> {
* A set of auth related metadata, containing the following
Expand All @@ -109,9 +120,13 @@ public function __construct(
* @type string $id_token
* }
*/
public function fetchAuthToken(callable $httpHandler = null)
public function fetchAuthToken(callable $httpHandler = null, array $metricsHeader = [])
{
return $this->auth->fetchAuthToken($httpHandler);
// We don't support id token endpoint requests as of now for User Cred
return $this->auth->fetchAuthToken(
$httpHandler,
$this->applyTokenEndpointMetrics($metricsHeader, 'at')
);
}

/**
Expand Down Expand Up @@ -149,4 +164,9 @@ public function getGrantedScope()
{
return $this->auth->getGrantedScope();
}

protected function getCredType(): string
{
return self::CRED_TYPE;
}
}
120 changes: 120 additions & 0 deletions src/MetricsTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php
/*
* Copyright 2024 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

namespace Google\Auth;

/**
* Trait containing helper methods required for enabling
* observability metrics in the library.
*
* @internal
*/
trait MetricsTrait
{
/**
* @var string The version of the auth library php.
*/
private static $version;

/**
* @var string The header key for the observability metrics.
*/
protected static $metricMetadataKey = 'x-goog-api-client';

/**
* @param string $credType [Optional] The credential type.
* Empty value will not add any credential type to the header.
* Should be one of `'sa'`, `'jwt'`, `'imp'`, `'mds'`, `'u'`.
* @param string $authRequestType [Optional] The auth request type.
* Empty value will not add any auth request type to the header.
* Should be one of `'at'`, `'it'`, `'mds'`.
* @return string The header value for the observability metrics.
*/
protected static function getMetricsHeader(
$credType = '',
$authRequestType = ''
): string {
$value = sprintf(
'gl-php/%s auth/%s',
PHP_VERSION,
self::getVersion()
);

if (!empty($authRequestType)) {
$value .= ' auth-request-type/' . $authRequestType;
}

if (!empty($credType)) {
$value .= ' cred-type/' . $credType;
}

return $value;
}

/**
* @param array<mixed> $metadata The metadata to update and return.
* @return array<mixed> The updated metadata.
*/
protected function applyServiceApiUsageMetrics($metadata)
{
if ($credType = $this->getCredType()) {
// Add service api usage observability metrics info into metadata
// We expect upstream libries to have the metadata key populated already
$value = 'cred-type/' . $credType;
if (!isset($metadata[self::$metricMetadataKey])) {
// This case will happen only when someone invokes the updateMetadata
// method on the credentials fetcher themselves.
$metadata[self::$metricMetadataKey] = [$value];
} elseif (is_array($metadata[self::$metricMetadataKey])) {
$metadata[self::$metricMetadataKey][0] .= ' ' . $value;
} else {
$metadata[self::$metricMetadataKey] .= ' ' . $value;
}
}

return $metadata;
}

/**
* @param array<mixed> $metadata The metadata to update and return.
* @param string $authRequestType The auth request type. Possible values are
* `'at'`, `'it'`, `'mds'`.
* @return array<mixed> The updated metadata.
*/
protected function applyTokenEndpointMetrics($metadata, $authRequestType)
{
$metricsHeader = self::getMetricsHeader($this->getCredType(), $authRequestType);
if (!isset($metadata[self::$metricMetadataKey])) {
$metadata[self::$metricMetadataKey] = $metricsHeader;
}
return $metadata;
}

protected static function getVersion(): string
{
if (is_null(self::$version)) {
$versionFilePath = __DIR__ . '/../VERSION';
self::$version = trim((string) file_get_contents($versionFilePath));
}
return self::$version;
}

protected function getCredType(): string
{
return '';
}
}
12 changes: 8 additions & 4 deletions src/OAuth2.php
Original file line number Diff line number Diff line change
Expand Up @@ -582,9 +582,11 @@ public function toJwt(array $config = [])
* Generates a request for token credentials.
*
* @param callable $httpHandler callback which delivers psr7 request
* @param array<mixed> $headers [optional] Additional headers to pass to
* the token endpoint request.
* @return RequestInterface the authorization Url.
*/
public function generateCredentialsRequest(callable $httpHandler = null)
public function generateCredentialsRequest(callable $httpHandler = null, $headers = [])
{
$uri = $this->getTokenCredentialUri();
if (is_null($uri)) {
Expand Down Expand Up @@ -646,7 +648,7 @@ public function generateCredentialsRequest(callable $httpHandler = null)
$headers = [
'Cache-Control' => 'no-store',
'Content-Type' => 'application/x-www-form-urlencoded',
];
] + $headers;

return new Request(
'POST',
Expand All @@ -660,15 +662,17 @@ public function generateCredentialsRequest(callable $httpHandler = null)
* Fetches the auth tokens based on the current state.
*
* @param callable $httpHandler callback which delivers psr7 request
* @param array<mixed> $headers [optional] If present, add these headers to the token
* endpoint request.
* @return array<mixed> the response
*/
public function fetchAuthToken(callable $httpHandler = null)
public function fetchAuthToken(callable $httpHandler = null, $headers = [])
{
if (is_null($httpHandler)) {
$httpHandler = HttpHandlerFactory::build(HttpClientCache::getHttpClient());
}

$response = $httpHandler($this->generateCredentialsRequest($httpHandler));
$response = $httpHandler($this->generateCredentialsRequest($httpHandler, $headers));
$credentials = $this->parseTokenResponse($response);
$this->updateToken($credentials);
if (isset($credentials['scope'])) {
Expand Down

0 comments on commit 6495f31

Please sign in to comment.