diff --git a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
index 92ab0cb34..43f1b6cfe 100644
--- a/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
+++ b/oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java
@@ -42,11 +42,15 @@
import com.google.api.client.util.GenericData;
import com.google.auth.ServiceAccountSigner;
import com.google.auth.http.HttpTransportFactory;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableSet;
+import java.io.BufferedReader;
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
@@ -100,6 +104,8 @@ public class ComputeEngineCredentials extends GoogleCredentials
private static final String METADATA_FLAVOR = "Metadata-Flavor";
private static final String GOOGLE = "Google";
+ private static final String WINDOWS = "windows";
+ private static final String LINUX = "linux";
private static final String PARSE_ERROR_PREFIX = "Error parsing token refresh response. ";
private static final String PARSE_ERROR_ACCOUNT = "Error parsing service account response. ";
@@ -281,14 +287,79 @@ private HttpResponse getMetadataResponse(String url) throws IOException {
return response;
}
- /** Return whether code is running on Google Compute Engine. */
- static boolean runningOnComputeEngine(
+ /**
+ * Implements an algorithm to detect whether the code is running on Google Compute Environment
+ * (GCE) or equivalent runtime. See AIP-4115 for more
+ * details The algorithm consists of active and passive checks:
+ * Active: to check that GCE Metadata service is present by sending a http request to send
+ * a request to {@code ComputeEngineCredentials.DEFAULT_METADATA_SERVER_URL}
+ *
+ *
Passive: to check if SMBIOS variable is present and contains expected value. This + * step is platform specific: + * + *
For Linux: check if the file "/sys/class/dmi/id/product_name" exists and contains a + * line that starts with Google. + * + *
For Windows: to be implemented + * + *
Other platforms: not supported + * + *
This algorithm can be disabled with environment variable {@code
+ * DefaultCredentialsProvider.NO_GCE_CHECK_ENV_VAR} set to {@code true}. In this case, the
+ * algorithm will always return {@code false} Returns {@code true} if currently running on Google
+ * Compute Environment (GCE) or equivalent runtime. Returns {@code false} if detection fails,
+ * platform is not supported or if detection disabled using the environment variable.
+ */
+ static synchronized boolean isOnGce(
HttpTransportFactory transportFactory, DefaultCredentialsProvider provider) {
// If the environment has requested that we do no GCE checks, return immediately.
if (Boolean.parseBoolean(provider.getEnv(DefaultCredentialsProvider.NO_GCE_CHECK_ENV_VAR))) {
return false;
}
+ boolean result = pingComputeEngineMetadata(transportFactory, provider);
+
+ if (!result) {
+ result = checkStaticGceDetection(provider);
+ }
+
+ if (!result) {
+ LOGGER.log(Level.FINE, "Failed to detect whether running on Google Compute Engine.");
+ }
+
+ return result;
+ }
+
+ @VisibleForTesting
+ static boolean checkProductNameOnLinux(BufferedReader reader) throws IOException {
+ String name = reader.readLine().trim();
+ return name.startsWith(GOOGLE);
+ }
+
+ @VisibleForTesting
+ static boolean checkStaticGceDetection(DefaultCredentialsProvider provider) {
+ String osName = provider.getOsName();
+ try {
+ if (osName.startsWith(LINUX)) {
+ // Checks GCE residency on Linux platform.
+ File linuxFile = new File("/sys/class/dmi/id/product_name");
+ return checkProductNameOnLinux(
+ new BufferedReader(new InputStreamReader(provider.readStream(linuxFile))));
+ } else if (osName.startsWith(WINDOWS)) {
+ // Checks GCE residency on Windows platform.
+ // TODO: implement registry check via FFI
+ return false;
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.FINE, "Encountered an unexpected exception when checking SMBIOS value", e);
+ return false;
+ }
+ // Platforms other than Linux and Windows are not supported.
+ return false;
+ }
+
+ private static boolean pingComputeEngineMetadata(
+ HttpTransportFactory transportFactory, DefaultCredentialsProvider provider) {
GenericUrl tokenUrl = new GenericUrl(getMetadataServerUrl(provider));
for (int i = 1; i <= MAX_COMPUTE_PING_TRIES; ++i) {
try {
@@ -311,12 +382,11 @@ static boolean runningOnComputeEngine(
} catch (IOException e) {
LOGGER.log(
Level.FINE,
- "Encountered an unexpected exception when determining"
- + " if we are running on Google Compute Engine.",
+ "Encountered an unexpected exception when checking"
+ + " if running on Google Compute Engine using Metadata Service ping.",
e);
}
}
- LOGGER.log(Level.FINE, "Failed to detect whether we are running on Google Compute Engine.");
return false;
}
diff --git a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java
index 12fff6a37..28ded069f 100644
--- a/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java
+++ b/oauth2_http/java/com/google/auth/oauth2/DefaultCredentialsProvider.java
@@ -53,29 +53,20 @@
* overriding the state and environment for testing purposes.
*/
class DefaultCredentialsProvider {
-
static final DefaultCredentialsProvider DEFAULT = new DefaultCredentialsProvider();
-
static final String CREDENTIAL_ENV_VAR = "GOOGLE_APPLICATION_CREDENTIALS";
-
static final String WELL_KNOWN_CREDENTIALS_FILE = "application_default_credentials.json";
-
static final String CLOUDSDK_CONFIG_DIRECTORY = "gcloud";
-
static final String HELP_PERMALINK =
"https://developers.google.com/accounts/docs/application-default-credentials";
-
static final String APP_ENGINE_SIGNAL_CLASS = "com.google.appengine.api.utils.SystemProperty";
-
static final String CLOUD_SHELL_ENV_VAR = "DEVSHELL_CLIENT_PORT";
-
static final String SKIP_APP_ENGINE_ENV_VAR = "GOOGLE_APPLICATION_CREDENTIALS_SKIP_APP_ENGINE";
static final String SPECIFICATION_VERSION = System.getProperty("java.specification.version");
static final String GAE_RUNTIME_VERSION =
System.getProperty("com.google.appengine.runtime.version");
static final String RUNTIME_JETTY_LOGGER = System.getProperty("org.eclipse.jetty.util.log.class");
static final Logger LOGGER = Logger.getLogger(DefaultCredentialsProvider.class.getName());
-
static final String NO_GCE_CHECK_ENV_VAR = "NO_GCE_CHECK";
static final String GCE_METADATA_HOST_ENV_VAR = "GCE_METADATA_HOST";
static final String CLOUDSDK_CLIENT_ID =
@@ -236,11 +227,10 @@ private void warnAboutProblematicCredentials(GoogleCredentials credentials) {
private final File getWellKnownCredentialsFile() {
File cloudConfigPath;
- String os = getProperty("os.name", "").toLowerCase(Locale.US);
String envPath = getEnv("CLOUDSDK_CONFIG");
if (envPath != null) {
cloudConfigPath = new File(envPath);
- } else if (os.indexOf("windows") >= 0) {
+ } else if (getOsName().indexOf("windows") >= 0) {
File appDataPath = new File(getEnv("APPDATA"));
cloudConfigPath = new File(appDataPath, CLOUDSDK_CONFIG_DIRECTORY);
} else {
@@ -310,8 +300,7 @@ private final GoogleCredentials tryGetComputeCredentials(HttpTransportFactory tr
if (checkedComputeEngine) {
return null;
}
- boolean runningOnComputeEngine =
- ComputeEngineCredentials.runningOnComputeEngine(transportFactory, this);
+ boolean runningOnComputeEngine = ComputeEngineCredentials.isOnGce(transportFactory, this);
checkedComputeEngine = true;
if (runningOnComputeEngine) {
return ComputeEngineCredentials.newBuilder()
@@ -337,6 +326,10 @@ protected boolean isOnGAEStandard7() {
&& (SPECIFICATION_VERSION.equals("1.7") || RUNTIME_JETTY_LOGGER == null);
}
+ String getOsName() {
+ return getProperty("os.name", "").toLowerCase(Locale.US);
+ }
+
/*
* Start of methods to allow overriding in the test code to isolate from the environment.
*/
diff --git a/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java b/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java
index 8db555318..48aca17c7 100644
--- a/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java
+++ b/oauth2_http/javatests/com/google/auth/oauth2/DefaultCredentialsProviderTest.java
@@ -49,10 +49,13 @@
import com.google.auth.oauth2.ComputeEngineCredentialsTest.MockMetadataServerTransportFactory;
import com.google.auth.oauth2.GoogleCredentialsTest.MockHttpTransportFactory;
import com.google.auth.oauth2.GoogleCredentialsTest.MockTokenServerTransportFactory;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.StringReader;
import java.net.URI;
import java.nio.file.Paths;
import java.security.AccessControlException;
@@ -89,6 +92,7 @@ public class DefaultCredentialsProviderTest {
private static final Collection