Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Enable Auth Observability Metrics #509

Merged
merged 25 commits into from
May 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e74bf89
feat: Initate Metrics Trait
yash30201 Dec 14, 2023
b01646a
feat: Enable Service Api Metrics
yash30201 Dec 14, 2023
73e9ee3
feat: Enable Token Endpoint Metrics
yash30201 Dec 14, 2023
e1f1e08
fix: Remove the anit pattern
yash30201 Dec 15, 2023
fa36906
fix: Correct applyMetricHeader method name
yash30201 Dec 15, 2023
134ebdc
Merge branch 'main' into metrics-with-trait
yash30201 Dec 15, 2023
7c2b605
Finalise PR
yash30201 Dec 19, 2023
bfe2cfe
nit fix
yash30201 Dec 19, 2023
3fc4be9
fix: Nit fix on docs
yash30201 Dec 19, 2023
1c383b0
PR self review
yash30201 Dec 19, 2023
aca9108
fix: Static analysis fix and updatemetadata logic correction
yash30201 Dec 19, 2023
2640b97
Self review:
yash30201 Dec 19, 2023
7a4b456
Merge branch 'main' into metrics-with-trait
bshaffer Dec 28, 2023
0293833
PR Iteration
yash30201 Jan 3, 2024
06b368f
Fix the service api usage metrics logic
yash30201 Jan 4, 2024
fce1cea
Update documentation
yash30201 Jan 4, 2024
43c525a
Remove directory separator usage
yash30201 Jan 4, 2024
661c3ba
Merge branch 'main' into metrics-with-trait
yash30201 Jan 4, 2024
d9ffad8
Merge branch 'main' into metrics-with-trait
yash30201 Jan 5, 2024
c70ff2e
Nit fix
yash30201 Jan 5, 2024
d4cc704
iteration
yash30201 Apr 30, 2024
8f7a74a
Merge branch 'main' into metrics-with-trait
bshaffer Apr 30, 2024
e71c48e
Update static reference
yash30201 Apr 30, 2024
16e0b72
Apply suggestions from code review
bshaffer May 2, 2024
1cf34d6
Merge branch 'main' into metrics-with-trait
bshaffer May 2, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
{
yash30201 marked this conversation as resolved.
Show resolved Hide resolved
$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
Loading