From 97c65a03eaeb2f41704cb8552c8fd7270fdf7025 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Fri, 21 Feb 2025 14:54:43 +0000 Subject: [PATCH 1/2] Add additional pre-verification check for IIS variables Tries to look for the presence of the DD_INSTRUMENTATION_INSTALL_TYPE variable with windows_fleet_installer - if we find it, then we try to rollback the variables --- .../Datadog.FleetInstaller/AppHostHelper.cs | 66 +++++++++++++++++++ .../Commands/InstallCommand.cs | 40 +++++++++++ tracer/src/Datadog.FleetInstaller/Defaults.cs | 2 + .../src/Datadog.FleetInstaller/ReturnCode.cs | 1 + .../Datadog.FleetInstaller/TracerValues.cs | 2 +- 5 files changed, 110 insertions(+), 1 deletion(-) diff --git a/tracer/src/Datadog.FleetInstaller/AppHostHelper.cs b/tracer/src/Datadog.FleetInstaller/AppHostHelper.cs index dfd36aa58243..faf24bbdbe5c 100644 --- a/tracer/src/Datadog.FleetInstaller/AppHostHelper.cs +++ b/tracer/src/Datadog.FleetInstaller/AppHostHelper.cs @@ -27,6 +27,53 @@ public static bool RemoveAllEnvironmentVariables(ILogger log) return ModifyEnvironmentVariablesWithRetry(log, envVars, RemoveEnvVars); } + public static bool GetAppPoolEnvironmentVariable(ILogger log, string environmentVariable, out string? value) + { + using var serverManager = new ServerManager(); + var appPoolsSection = GetApplicationPoolsSection(log, serverManager); + if (appPoolsSection is null) + { + value = null; + return false; + } + + var (applicationPoolDefaults, applicationPoolsCollection) = appPoolsSection.Value; + + // Check defaults + log.WriteInfo($"Checking applicationPoolDefaults for environment variable: {environmentVariable}"); + if (TryGetEnvVar(applicationPoolDefaults.GetCollection("environmentVariables"), environmentVariable) is { } envValue) + { + value = envValue; + return true; + } + + foreach (var appPoolElement in applicationPoolsCollection) + { + if (string.Equals(appPoolElement.ElementTagName, "add", StringComparison.OrdinalIgnoreCase)) + { + // An app pool element + var poolName = appPoolElement.GetAttributeValue("name") as string; + if (poolName is null) + { + // poolName can never be null, if it is, weirdness is afoot, so bail out + log.WriteInfo("Found app pool element without a name, skipping"); + continue; + } + + log.WriteInfo($"Checking app pool '{poolName}' for environment variable: {environmentVariable}"); + if (TryGetEnvVar(appPoolElement.GetCollection("environmentVariables"), environmentVariable) is { } poolEnvValue) + { + value = poolEnvValue; + return true; + } + } + } + + log.WriteInfo($"{environmentVariable} variable not found in any app pools"); + value = null; + return false; + } + private static bool ModifyEnvironmentVariablesWithRetry( ILogger log, ReadOnlyDictionary envVars, @@ -275,4 +322,23 @@ private static void RemoveEnvVars(ConfigurationElementCollection envVars, ReadOn envVars.Remove(element); } } + + private static string? TryGetEnvVar(ConfigurationElementCollection envVars, string variable) + { + foreach (var envVarEle in envVars) + { + if (!string.Equals(envVarEle.ElementTagName, "add", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + if (envVarEle.GetAttributeValue("name") is string key + && key.Equals(variable, StringComparison.OrdinalIgnoreCase)) + { + return envVarEle["value"] as string; + } + } + + return null; + } } diff --git a/tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs b/tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs index 99e43df3d2fa..9f5243338596 100644 --- a/tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs +++ b/tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs @@ -3,6 +3,7 @@ // This product includes software developed at Datadog (https://www.datadoghq.com/). Copyright 2017 Datadog, Inc. // +using System; using System.CommandLine; using System.CommandLine.Invocation; using System.CommandLine.Parsing; @@ -55,6 +56,37 @@ internal static ReturnCode Execute( { log.WriteInfo("Installing .NET tracer"); + bool tryIisRollback; + + try + { + log.WriteInfo("Checking IIS app pools for pre-existing instrumentation variable"); + if (AppHostHelper.GetAppPoolEnvironmentVariable(log, Defaults.InstrumentationInstallTypeKey, out var value)) + { + var expectedValue = Defaults.InstrumentationInstallTypeValue; + if (expectedValue.Equals(value, StringComparison.Ordinal)) + { + log.WriteInfo("Found existing instrumentation install type. Won't rollback IIS instrumentation if install fails"); + tryIisRollback = false; + } + else + { + log.WriteInfo($"Found instrumentation install type, but did not have expected value {expectedValue}. Will rollback IIS instrumentation if install fails"); + tryIisRollback = true; + } + } + else + { + log.WriteInfo("No existing fleet installer instrumentation install type found. Will rollback IIS instrumentation if install fails"); + tryIisRollback = true; + } + } + catch (Exception ex) + { + log.WriteError(ex, "Error reading IIS app pools, installation failed"); + return ReturnCode.ErrorReadingIisConfiguration; + } + if (!FileHelper.CreateLogDirectory(log, tracerLogDirectory)) { // This probably isn't a reason to bail out @@ -69,6 +101,14 @@ internal static ReturnCode Execute( if (!AppHostHelper.SetAllEnvironmentVariables(log, tracerValues)) { // hard to be sure exactly of the state at this point + if (tryIisRollback) + { + log.WriteInfo("Attempting IIS variable rollback"); + + // We ignore failures here + AppHostHelper.RemoveAllEnvironmentVariables(log); + } + return ReturnCode.ErrorSettingAppPoolVariables; } diff --git a/tracer/src/Datadog.FleetInstaller/Defaults.cs b/tracer/src/Datadog.FleetInstaller/Defaults.cs index 181e96f277d4..9e23d9be5234 100644 --- a/tracer/src/Datadog.FleetInstaller/Defaults.cs +++ b/tracer/src/Datadog.FleetInstaller/Defaults.cs @@ -11,6 +11,8 @@ namespace Datadog.FleetInstaller; internal class Defaults { public const string CrashTrackingRegistryKey = @"Software\Microsoft\Windows\Windows Error Reporting\RuntimeExceptionHelperModules"; + public const string InstrumentationInstallTypeKey = "DD_INSTRUMENTATION_INSTALL_TYPE"; + public const string InstrumentationInstallTypeValue = "windows_fleet_installer"; public static string TracerLogDirectory => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "Datadog .NET Tracer", "logs"); diff --git a/tracer/src/Datadog.FleetInstaller/ReturnCode.cs b/tracer/src/Datadog.FleetInstaller/ReturnCode.cs index d22b69a07adf..b84dce65df2c 100644 --- a/tracer/src/Datadog.FleetInstaller/ReturnCode.cs +++ b/tracer/src/Datadog.FleetInstaller/ReturnCode.cs @@ -17,4 +17,5 @@ internal enum ReturnCode ErrorRemovingAppPoolVariables, ErrorRemovingNativeLoaderFiles, ErrorRemovingCrashTrackerKey, + ErrorReadingIisConfiguration, } diff --git a/tracer/src/Datadog.FleetInstaller/TracerValues.cs b/tracer/src/Datadog.FleetInstaller/TracerValues.cs index 3561b4b4fd23..ab55dcd63843 100644 --- a/tracer/src/Datadog.FleetInstaller/TracerValues.cs +++ b/tracer/src/Datadog.FleetInstaller/TracerValues.cs @@ -27,7 +27,7 @@ public TracerValues(string tracerHomeDirectory) { "DD_DOTNET_TRACER_HOME", TracerHomeDirectory }, { "COR_ENABLE_PROFILING", "1" }, { "CORECLR_ENABLE_PROFILING", "1" }, - { "DD_INSTRUMENTATION_INSTALL_TYPE", "windows_fleet_installer" }, + { Defaults.InstrumentationInstallTypeKey, Defaults.InstrumentationInstallTypeValue }, }); FilesToAddToGac = [ From 0d0e103642080e2522fb8b23c6e77149eb649101 Mon Sep 17 00:00:00 2001 From: Andrew Lock Date: Mon, 24 Feb 2025 11:29:22 +0000 Subject: [PATCH 2/2] PR feedback --- tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs b/tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs index 9f5243338596..37fbe5e33813 100644 --- a/tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs +++ b/tracer/src/Datadog.FleetInstaller/Commands/InstallCommand.cs @@ -66,12 +66,12 @@ internal static ReturnCode Execute( var expectedValue = Defaults.InstrumentationInstallTypeValue; if (expectedValue.Equals(value, StringComparison.Ordinal)) { - log.WriteInfo("Found existing instrumentation install type. Won't rollback IIS instrumentation if install fails"); + log.WriteInfo($"Found existing instrumentation install type with value {expectedValue}. Won't rollback IIS instrumentation if install fails"); tryIisRollback = false; } else { - log.WriteInfo($"Found instrumentation install type, but did not have expected value {expectedValue}. Will rollback IIS instrumentation if install fails"); + log.WriteInfo($"Found instrumentation install type {value}, but did not have expected value {expectedValue}. Will rollback IIS instrumentation if install fails"); tryIisRollback = true; } }