diff --git a/Src/Support/Google.Apis.Auth/OAuth2/ComputeCredential.cs b/Src/Support/Google.Apis.Auth/OAuth2/ComputeCredential.cs index 1c709560e29..257868e3968 100644 --- a/Src/Support/Google.Apis.Auth/OAuth2/ComputeCredential.cs +++ b/Src/Support/Google.Apis.Auth/OAuth2/ComputeCredential.cs @@ -21,9 +21,11 @@ limitations under the License. using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; @@ -46,8 +48,7 @@ public class ComputeCredential : ServiceCredential, IOidcTokenProvider, IGoogleC public const string MetadataServerUrl = GoogleAuthConsts.DefaultMetadataServerUrl; /// Caches result from first call to IsRunningOnComputeEngine - private readonly static Lazy> isRunningOnComputeEngineCached = new Lazy>( - () => IsRunningOnComputeEngineNoCache()); + private readonly static Lazy> isRunningOnComputeEngineCached = new Lazy>(IsRunningOnComputeEngineNoCacheAsync); /// /// Originally 1000ms was used without a retry. This proved inadequate; even 2000ms without @@ -295,7 +296,11 @@ public static Task IsRunningOnComputeEngine() return isRunningOnComputeEngineCached.Value; } - private static async Task IsRunningOnComputeEngineNoCache() + private static async Task IsRunningOnComputeEngineNoCacheAsync() => + await IsMetadataServerAvailableAsync().ConfigureAwait(false) + || await IsGoogleBiosAsync().ConfigureAwait(false); + + private static async Task IsMetadataServerAvailableAsync() { Logger.Info("Checking connectivity to ComputeEngine metadata server."); @@ -337,8 +342,83 @@ private static async Task IsRunningOnComputeEngineNoCache() } } // Only log after all attempts have failed. - Logger.Debug("Could not reach the Google Compute Engine metadata service. That is expected if this application is not running on GCE."); + Logger.Debug("Could not reach the Google Compute Engine metadata service. " + + "That is expected if this application is not running on GCE " + + "or on some cases where the metadata service is not available during application startup."); return false; } + + private async static Task IsGoogleBiosAsync() + { + Logger.Info("Checking BIOS values to determine GCE residency."); + try + { + if (IsLinux()) + { + return await IsLinuxGoogleBiosAsync().ConfigureAwait(false); + } + else if (IsWindows()) + { + return IsWindowsGoogleBios(); + } + else + { + Logger.Info("GCE residency detection through BIOS checking is only supported in Windows and Linux platforms."); + return false; + } + } + catch (Exception ex) + { + Logger.Debug($"Could not read BIOS: {ex}"); + return false; + } + + // Some of these will be simpler once we have acted on + // https://github.com/googleapis/google-api-dotnet-client/issues/2561. + + bool IsWindows() + { +#if NET45 || NET461 + // RuntimeInformation.IsOsPlatform is not available for these targets. + // But we know we are on Windows. + return true; +#else + return RuntimeInformation.IsOSPlatform(OSPlatform.Windows); +#endif + } + + bool IsLinux() + { +#if NET45 || NET461 + // RuntimeInformation.IsOsPlatform is not available for these targets. + // But we know we are on Windows. + return false; +#else + return RuntimeInformation.IsOSPlatform(OSPlatform.Linux); +#endif + } + + bool IsWindowsGoogleBios() => throw new NotImplementedException(); + + async Task IsLinuxGoogleBiosAsync() + { + Logger.Info("Checking BIOS values on Linux."); + + string fileName = "/sys/class/dmi/id/product_name"; + if (!File.Exists(fileName)) + { + Logger.Debug($"Couldn't read file {fileName} containing BIOS mapped values."); + return false; + } + + string productName; + using (var streamReader = new StreamReader(new FileStream("/sys/class/dmi/id/product_name", FileMode.Open, FileAccess.Read))) + { + productName = await streamReader.ReadLineAsync().ConfigureAwait(false); + } + productName = productName?.Trim(); + return productName == "Google" || productName == "Google Compute Engine"; + } + } } }