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: check product name file on linux for gce #469

Merged
merged 4 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
21 changes: 21 additions & 0 deletions src/Credentials/GCECredentials.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ class GCECredentials extends CredentialsLoader implements
*/
const FLAVOR_HEADER = 'Metadata-Flavor';

/**
* The Linux file which contains the product name.
*/
private const GKE_PRODUCT_NAME_FILE = '/sys/class/dmi/id/product_name';

/**
* 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 @@ -340,6 +345,22 @@ public static function onGce(callable $httpHandler = null)
} catch (ConnectException $e) {
}
}

if (PHP_OS === 'Windows') {
// @TODO: implement GCE residency detection on Windows
noahdietz marked this conversation as resolved.
Show resolved Hide resolved
return false;
}

// Detect GCE residency on Linux
return self::detectResidencyLinux(self::GKE_PRODUCT_NAME_FILE);
}

private static function detectResidencyLinux(string $productNameFile): bool
{
if (file_exists($productNameFile)) {
$productName = trim((string) file_get_contents($productNameFile));
return 0 === strpos($productName, 'Google');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe lowercase is better?

Suggested change
return 0 === strpos($productName, 'Google');
return 0 === strpos($productName, 'google');

Copy link
Contributor Author

@bshaffer bshaffer Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The is determined by the Operating system, and the string is uppercase (literally, Google). So making it lowercase would fail the check.

Why do you think lowercase is better?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My only question is: Should we normalize the contents (e.g. force lowercase) in order to avoid any sort of subtle issues with string matching/discrepancies in this file's contents?

Copy link
Contributor Author

@bshaffer bshaffer Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Product Name is either Google or Google Compute Engine. The lowercase name google is not valid (or at least, there are no known Operating Systems which use that). So there's no reason to add a check for the lowercase value.

To my knowledge, other language implementations have not included such a normalization

}
return false;
}

Expand Down
32 changes: 32 additions & 0 deletions tests/Credentials/GCECredentialsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,38 @@ public function testOnGCEIsFalseOnServerErrorStatus()
$this->assertFalse(GCECredentials::onGCE($httpHandler));
}

public function testCheckProductNameFile()
{
$tmpFile = tempnam(sys_get_temp_dir(), 'gce-test-product-name');

$method = (new \ReflectionClass(GCECredentials::class))
->getMethod('detectResidencyLinux');
$method->setAccessible(true);

$this->assertFalse($method->invoke(null, '/nonexistant/file'));

file_put_contents($tmpFile, 'Google');
$this->assertTrue($method->invoke(null, $tmpFile));

file_put_contents($tmpFile, 'Not Google');
$this->assertFalse($method->invoke(null, $tmpFile));
}

public function testOnGceWithResidency()
{
if (!GCECredentials::onGCE()) {
$this->markTestSkipped('This test only works while running on GCE');
}

// If calling metadata server fails, this will check the residency file.
$httpHandler = function () {
// Mock an exception, such as a ping timeout
throw $this->prophesize(ClientException::class)->reveal();
};

$this->assertTrue(GCECredentials::onGCE($httpHandler));
}

public function testOnGCEIsFalseOnOkStatusWithoutExpectedHeader()
{
$httpHandler = getHandler([
Expand Down