From 9d74b7c0e8c166aa10563eabe05d2fa3b4f6d63a Mon Sep 17 00:00:00 2001
From: Caroline Rose <50421366+rosecm@users.noreply.github.com>
Date: Fri, 14 Nov 2025 11:39:11 -0500
Subject: [PATCH] feat(auth): Add option for metadata server application
default credentials without project override (#12948)
---
docs/get-started/authentication.md | 12 +++++-
packages/cli/src/config/auth.test.ts | 4 +-
packages/cli/src/config/auth.ts | 2 +-
packages/cli/src/gemini.tsx | 14 +++++--
packages/cli/src/ui/auth/AuthDialog.test.tsx | 37 ++++++++++++++++++-
packages/cli/src/ui/auth/AuthDialog.tsx | 14 +++++--
.../core/src/code_assist/codeAssist.test.ts | 6 +--
packages/core/src/code_assist/codeAssist.ts | 2 +-
packages/core/src/code_assist/oauth2.test.ts | 10 ++---
packages/core/src/code_assist/oauth2.ts | 15 +++++---
.../core/src/core/contentGenerator.test.ts | 19 +++++++++-
packages/core/src/core/contentGenerator.ts | 7 ++--
packages/core/src/telemetry/loggers.test.ts | 4 +-
packages/core/src/telemetry/metrics.ts | 2 +-
14 files changed, 113 insertions(+), 35 deletions(-)
diff --git a/docs/get-started/authentication.md b/docs/get-started/authentication.md
index 712d6ee065..97d4d9fa50 100644
--- a/docs/get-started/authentication.md
+++ b/docs/get-started/authentication.md
@@ -8,13 +8,23 @@ CLI, configure **one** of the following authentication methods:
- Use Gemini API key
- Use Vertex AI
- Headless (non-interactive) mode
-- Google Cloud Shell
+- Google Cloud Environments (Cloud Shell, Compute Engine, etc.)
## Quick Check: Running in Google Cloud Shell?
If you are running the Gemini CLI within a Google Cloud Shell environment,
authentication is typically automatic using your Cloud Shell credentials.
+### Other Google Cloud Environments (e.g., Compute Engine)
+
+Some other Google Cloud environments, such as Compute Engine VMs, might also
+support automatic authentication. In these environments, Gemini CLI can
+automatically use Application Default Credentials (ADC) sourced from the
+environment's metadata server.
+
+If automatic authentication does not occur in your environment, you will need to
+use one of the interactive methods described below.
+
## Authenticate in Interactive mode
When you run Gemini CLI through the command-line, Gemini CLI will provide the
diff --git a/packages/cli/src/config/auth.test.ts b/packages/cli/src/config/auth.test.ts
index ae1af240a3..7bbaafdfcb 100644
--- a/packages/cli/src/config/auth.test.ts
+++ b/packages/cli/src/config/auth.test.ts
@@ -32,8 +32,8 @@ describe('validateAuthMethod', () => {
expect(validateAuthMethod(AuthType.LOGIN_WITH_GOOGLE)).toBeNull();
});
- it('should return null for CLOUD_SHELL', () => {
- expect(validateAuthMethod(AuthType.CLOUD_SHELL)).toBeNull();
+ it('should return null for COMPUTE_ADC', () => {
+ expect(validateAuthMethod(AuthType.COMPUTE_ADC)).toBeNull();
});
describe('USE_GEMINI', () => {
diff --git a/packages/cli/src/config/auth.ts b/packages/cli/src/config/auth.ts
index 7492e09b7b..87940307e1 100644
--- a/packages/cli/src/config/auth.ts
+++ b/packages/cli/src/config/auth.ts
@@ -11,7 +11,7 @@ export function validateAuthMethod(authMethod: string): string | null {
loadEnvironment(loadSettings().merged);
if (
authMethod === AuthType.LOGIN_WITH_GOOGLE ||
- authMethod === AuthType.CLOUD_SHELL
+ authMethod === AuthType.COMPUTE_ADC
) {
return null;
}
diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx
index f7a2f48064..7d7d072022 100644
--- a/packages/cli/src/gemini.tsx
+++ b/packages/cli/src/gemini.tsx
@@ -284,13 +284,19 @@ export async function main() {
validateDnsResolutionOrder(settings.merged.advanced?.dnsResolutionOrder),
);
- // Set a default auth type if one isn't set.
- if (!settings.merged.security?.auth?.selectedType) {
- if (process.env['CLOUD_SHELL'] === 'true') {
+ // Set a default auth type if one isn't set or is set to a legacy type
+ if (
+ !settings.merged.security?.auth?.selectedType ||
+ settings.merged.security?.auth?.selectedType === AuthType.LEGACY_CLOUD_SHELL
+ ) {
+ if (
+ process.env['CLOUD_SHELL'] === 'true' ||
+ process.env['GEMINI_CLI_USE_COMPUTE_ADC'] === 'true'
+ ) {
settings.setValue(
SettingScope.User,
'selectedAuthType',
- AuthType.CLOUD_SHELL,
+ AuthType.COMPUTE_ADC,
);
}
}
diff --git a/packages/cli/src/ui/auth/AuthDialog.test.tsx b/packages/cli/src/ui/auth/AuthDialog.test.tsx
index 0059e8202a..ec7f70dd36 100644
--- a/packages/cli/src/ui/auth/AuthDialog.test.tsx
+++ b/packages/cli/src/ui/auth/AuthDialog.test.tsx
@@ -109,8 +109,41 @@ describe('AuthDialog', () => {
const items = mockedRadioButtonSelect.mock.calls[0][0].items;
expect(items).toContainEqual({
label: 'Use Cloud Shell user credentials',
- value: AuthType.CLOUD_SHELL,
- key: AuthType.CLOUD_SHELL,
+ value: AuthType.COMPUTE_ADC,
+ key: AuthType.COMPUTE_ADC,
+ });
+ });
+
+ it('does not show metadata server application default credentials option in Cloud Shell environment', () => {
+ process.env['CLOUD_SHELL'] = 'true';
+ renderWithProviders();
+ const items = mockedRadioButtonSelect.mock.calls[0][0].items;
+ expect(items).not.toContainEqual({
+ label: 'Use metadata server application default credentials',
+ value: AuthType.COMPUTE_ADC,
+ key: AuthType.COMPUTE_ADC,
+ });
+ });
+
+ it('shows metadata server application default credentials option when GEMINI_CLI_USE_COMPUTE_ADC env var is true', () => {
+ process.env['GEMINI_CLI_USE_COMPUTE_ADC'] = 'true';
+ renderWithProviders();
+ const items = mockedRadioButtonSelect.mock.calls[0][0].items;
+ expect(items).toContainEqual({
+ label: 'Use metadata server application default credentials',
+ value: AuthType.COMPUTE_ADC,
+ key: AuthType.COMPUTE_ADC,
+ });
+ });
+
+ it('does not show Cloud Shell option when when GEMINI_CLI_USE_COMPUTE_ADC env var is true', () => {
+ process.env['GEMINI_CLI_USE_COMPUTE_ADC'] = 'true';
+ renderWithProviders();
+ const items = mockedRadioButtonSelect.mock.calls[0][0].items;
+ expect(items).not.toContainEqual({
+ label: 'Use Cloud Shell user credentials',
+ value: AuthType.COMPUTE_ADC,
+ key: AuthType.COMPUTE_ADC,
});
});
diff --git a/packages/cli/src/ui/auth/AuthDialog.tsx b/packages/cli/src/ui/auth/AuthDialog.tsx
index 61ea01764d..ecd51f6ed4 100644
--- a/packages/cli/src/ui/auth/AuthDialog.tsx
+++ b/packages/cli/src/ui/auth/AuthDialog.tsx
@@ -50,11 +50,19 @@ export function AuthDialog({
? [
{
label: 'Use Cloud Shell user credentials',
- value: AuthType.CLOUD_SHELL,
- key: AuthType.CLOUD_SHELL,
+ value: AuthType.COMPUTE_ADC,
+ key: AuthType.COMPUTE_ADC,
},
]
- : []),
+ : process.env['GEMINI_CLI_USE_COMPUTE_ADC'] === 'true'
+ ? [
+ {
+ label: 'Use metadata server application default credentials',
+ value: AuthType.COMPUTE_ADC,
+ key: AuthType.COMPUTE_ADC,
+ },
+ ]
+ : []),
{
label: 'Use Gemini API Key',
value: AuthType.USE_GEMINI,
diff --git a/packages/core/src/code_assist/codeAssist.test.ts b/packages/core/src/code_assist/codeAssist.test.ts
index 1608a7d976..0974e2237e 100644
--- a/packages/core/src/code_assist/codeAssist.test.ts
+++ b/packages/core/src/code_assist/codeAssist.test.ts
@@ -68,18 +68,18 @@ describe('codeAssist', () => {
expect(generator).toBeInstanceOf(MockedCodeAssistServer);
});
- it('should create a server for CLOUD_SHELL', async () => {
+ it('should create a server for COMPUTE_ADC', async () => {
mockedGetOauthClient.mockResolvedValue(mockAuthClient as never);
mockedSetupUser.mockResolvedValue(mockUserData);
const generator = await createCodeAssistContentGenerator(
httpOptions,
- AuthType.CLOUD_SHELL,
+ AuthType.COMPUTE_ADC,
mockConfig,
);
expect(getOauthClient).toHaveBeenCalledWith(
- AuthType.CLOUD_SHELL,
+ AuthType.COMPUTE_ADC,
mockConfig,
);
expect(setupUser).toHaveBeenCalledWith(mockAuthClient);
diff --git a/packages/core/src/code_assist/codeAssist.ts b/packages/core/src/code_assist/codeAssist.ts
index c8ade92edd..f8c9ac47b8 100644
--- a/packages/core/src/code_assist/codeAssist.ts
+++ b/packages/core/src/code_assist/codeAssist.ts
@@ -21,7 +21,7 @@ export async function createCodeAssistContentGenerator(
): Promise {
if (
authType === AuthType.LOGIN_WITH_GOOGLE ||
- authType === AuthType.CLOUD_SHELL
+ authType === AuthType.COMPUTE_ADC
) {
const authClient = await getOauthClient(authType, config);
const userData = await setupUser(authClient);
diff --git a/packages/core/src/code_assist/oauth2.test.ts b/packages/core/src/code_assist/oauth2.test.ts
index b15a7aa89b..024f4ca739 100644
--- a/packages/core/src/code_assist/oauth2.test.ts
+++ b/packages/core/src/code_assist/oauth2.test.ts
@@ -318,7 +318,7 @@ describe('oauth2', () => {
});
it('should use Compute to get a client if no cached credentials exist', async () => {
- await getOauthClient(AuthType.CLOUD_SHELL, mockConfig);
+ await getOauthClient(AuthType.COMPUTE_ADC, mockConfig);
expect(Compute).toHaveBeenCalledWith({});
expect(mockGetAccessToken).toHaveBeenCalled();
@@ -329,7 +329,7 @@ describe('oauth2', () => {
mockComputeClient.credentials = newCredentials;
mockGetAccessToken.mockResolvedValue({ token: 'new-adc-token' });
- await getOauthClient(AuthType.CLOUD_SHELL, mockConfig);
+ await getOauthClient(AuthType.COMPUTE_ADC, mockConfig);
const credsPath = path.join(
tempHomeDir,
@@ -340,7 +340,7 @@ describe('oauth2', () => {
});
it('should return the Compute client on successful ADC authentication', async () => {
- const client = await getOauthClient(AuthType.CLOUD_SHELL, mockConfig);
+ const client = await getOauthClient(AuthType.COMPUTE_ADC, mockConfig);
expect(client).toBe(mockComputeClient);
});
@@ -349,9 +349,9 @@ describe('oauth2', () => {
mockGetAccessToken.mockRejectedValue(testError);
await expect(
- getOauthClient(AuthType.CLOUD_SHELL, mockConfig),
+ getOauthClient(AuthType.COMPUTE_ADC, mockConfig),
).rejects.toThrow(
- 'Could not authenticate using Cloud Shell credentials. Please select a different authentication method or ensure you are in a properly configured environment. Error: ADC Failed',
+ 'Could not authenticate using metadata server application default credentials. Please select a different authentication method or ensure you are in a properly configured environment. Error: ADC Failed',
);
});
});
diff --git a/packages/core/src/code_assist/oauth2.ts b/packages/core/src/code_assist/oauth2.ts
index 46ff9fcb00..4382195adb 100644
--- a/packages/core/src/code_assist/oauth2.ts
+++ b/packages/core/src/code_assist/oauth2.ts
@@ -155,12 +155,15 @@ async function initOauthClient(
}
}
- // In Google Cloud Shell, we can use Application Default Credentials (ADC)
- // provided via its metadata server to authenticate non-interactively using
- // the identity of the user logged into Cloud Shell.
- if (authType === AuthType.CLOUD_SHELL) {
+ // In Google Compute Engine based environments (including Cloud Shell), we can
+ // use Application Default Credentials (ADC) provided via its metadata server
+ // to authenticate non-interactively using the identity of the logged-in user.
+ if (authType === AuthType.COMPUTE_ADC) {
try {
- debugLogger.log("Attempting to authenticate via Cloud Shell VM's ADC.");
+ debugLogger.log(
+ 'Attempting to authenticate via metadata server application default credentials.',
+ );
+
const computeClient = new Compute({
// We can leave this empty, since the metadata server will provide
// the service account email.
@@ -172,7 +175,7 @@ async function initOauthClient(
return computeClient;
} catch (e) {
throw new Error(
- `Could not authenticate using Cloud Shell credentials. Please select a different authentication method or ensure you are in a properly configured environment. Error: ${getErrorMessage(
+ `Could not authenticate using metadata server application default credentials. Please select a different authentication method or ensure you are in a properly configured environment. Error: ${getErrorMessage(
e,
)}`,
);
diff --git a/packages/core/src/core/contentGenerator.test.ts b/packages/core/src/core/contentGenerator.test.ts
index ac5cb2f13e..c31585f7a8 100644
--- a/packages/core/src/core/contentGenerator.test.ts
+++ b/packages/core/src/core/contentGenerator.test.ts
@@ -67,7 +67,7 @@ describe('createContentGenerator', () => {
expect(generator).toBeInstanceOf(RecordingContentGenerator);
});
- it('should create a CodeAssistContentGenerator', async () => {
+ it('should create a CodeAssistContentGenerator when AuthType is LOGIN_WITH_GOOGLE', async () => {
const mockGenerator = {} as unknown as ContentGenerator;
vi.mocked(createCodeAssistContentGenerator).mockResolvedValue(
mockGenerator as never,
@@ -84,6 +84,23 @@ describe('createContentGenerator', () => {
);
});
+ it('should create a CodeAssistContentGenerator when AuthType is COMPUTE_ADC', async () => {
+ const mockGenerator = {} as unknown as ContentGenerator;
+ vi.mocked(createCodeAssistContentGenerator).mockResolvedValue(
+ mockGenerator as never,
+ );
+ const generator = await createContentGenerator(
+ {
+ authType: AuthType.COMPUTE_ADC,
+ },
+ mockConfig,
+ );
+ expect(createCodeAssistContentGenerator).toHaveBeenCalled();
+ expect(generator).toEqual(
+ new LoggingContentGenerator(mockGenerator, mockConfig),
+ );
+ });
+
it('should create a GoogleGenAI content generator', async () => {
const mockConfig = {
getUsageStatisticsEnabled: () => true,
diff --git a/packages/core/src/core/contentGenerator.ts b/packages/core/src/core/contentGenerator.ts
index 6fac941e01..4e37a9c0cc 100644
--- a/packages/core/src/core/contentGenerator.ts
+++ b/packages/core/src/core/contentGenerator.ts
@@ -48,7 +48,8 @@ export enum AuthType {
LOGIN_WITH_GOOGLE = 'oauth-personal',
USE_GEMINI = 'gemini-api-key',
USE_VERTEX_AI = 'vertex-ai',
- CLOUD_SHELL = 'cloud-shell',
+ LEGACY_CLOUD_SHELL = 'cloud-shell',
+ COMPUTE_ADC = 'compute-default-credentials',
}
export type ContentGeneratorConfig = {
@@ -79,7 +80,7 @@ export async function createContentGeneratorConfig(
// If we are using Google auth or we are in Cloud Shell, there is nothing else to validate for now
if (
authType === AuthType.LOGIN_WITH_GOOGLE ||
- authType === AuthType.CLOUD_SHELL
+ authType === AuthType.COMPUTE_ADC
) {
return contentGeneratorConfig;
}
@@ -120,7 +121,7 @@ export async function createContentGenerator(
};
if (
config.authType === AuthType.LOGIN_WITH_GOOGLE ||
- config.authType === AuthType.CLOUD_SHELL
+ config.authType === AuthType.COMPUTE_ADC
) {
const httpOptions = { headers: baseHeaders };
return new LoggingContentGenerator(
diff --git a/packages/core/src/telemetry/loggers.test.ts b/packages/core/src/telemetry/loggers.test.ts
index 46357d699f..4be2ec6eeb 100644
--- a/packages/core/src/telemetry/loggers.test.ts
+++ b/packages/core/src/telemetry/loggers.test.ts
@@ -298,7 +298,7 @@ describe('loggers', () => {
const event = new UserPromptEvent(
11,
'prompt-id-9',
- AuthType.CLOUD_SHELL,
+ AuthType.COMPUTE_ADC,
'test-prompt',
);
@@ -315,7 +315,7 @@ describe('loggers', () => {
interactive: false,
prompt_length: 11,
prompt_id: 'prompt-id-9',
- auth_type: 'cloud-shell',
+ auth_type: 'compute-default-credentials',
},
});
});
diff --git a/packages/core/src/telemetry/metrics.ts b/packages/core/src/telemetry/metrics.ts
index f2078aa62d..694643a2cc 100644
--- a/packages/core/src/telemetry/metrics.ts
+++ b/packages/core/src/telemetry/metrics.ts
@@ -886,7 +886,7 @@ export function getConventionAttributes(event: {
function getGenAiProvider(authType?: string): GenAiProviderName {
switch (authType) {
case AuthType.USE_VERTEX_AI:
- case AuthType.CLOUD_SHELL:
+ case AuthType.COMPUTE_ADC:
case AuthType.LOGIN_WITH_GOOGLE:
return GenAiProviderName.GCP_VERTEX_AI;
case AuthType.USE_GEMINI: