diff --git a/src/Aks/Aks/Commands/NewAzureRmAks.cs b/src/Aks/Aks/Commands/NewAzureRmAks.cs index 0dc55086dcbc..c97d16da0275 100644 --- a/src/Aks/Aks/Commands/NewAzureRmAks.cs +++ b/src/Aks/Aks/Commands/NewAzureRmAks.cs @@ -30,6 +30,7 @@ using Microsoft.Rest; using Microsoft.WindowsAzure.Commands.Common; using Microsoft.WindowsAzure.Commands.Utilities.Common; +using System.Runtime.InteropServices; namespace Microsoft.Azure.Commands.Aks { @@ -311,6 +312,61 @@ private void PreValidate() } } + // From OpenSSH 9.4 onwards, the default key type is ed25519, which is not supported in AKS. + // To ensure backward compatibility, we need to check the OpenSSH version installed and adjust the parameters accordingly. + static Version GetOpenSSHVersion() + { + using (Process process = new Process()) + { + try + { + // Using runtime information to determine the path to ssh.exe based on OS type. + bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + string defaultWindowsSshPath = @"C:\Windows\System32\OpenSSH\ssh.exe"; + process.StartInfo.FileName = isWindows && File.Exists(defaultWindowsSshPath) ? defaultWindowsSshPath : "ssh"; + process.StartInfo.Arguments = "-V"; + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardInput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.CreateNoWindow = true; + process.Start(); + + string standardOutput = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); + + process.WaitForExit(); + // OpenSSH version output follows formats like: + // Windows: "OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.3" + // Windows: "OpenSSH_for_Windows_9.5p1, LibreSSL 3.8.2" + // Linux/macOS: "OpenSSH_9.5p1, OpenSSL 3.0.13 30 Jan 2024" + // We match the common "OpenSSH_" prefix and make "for_Windows_" optional so this works across platforms. + var regMatch = System.Text.RegularExpressions.Regex.Match(standardOutput, @"OpenSSH_(?:for_Windows_)?(\d+)\.(\d+)"); + + // We don't really care about the patch version, so only return major and minor version. + return regMatch.Success ? new Version(int.Parse(regMatch.Groups[1].Value), int.Parse(regMatch.Groups[2].Value)) : null; + } + // We're not expecting ssh to be missing, but just in case, we catch any exception and return null. + catch + { + return null; + } + finally //Ensure process is properly disposed of and exited + { + if (!process.HasExited) + { + try + { + process.Kill(); + } + catch + { + // Ignore exceptions from Kill if process already exited + } + } + } + } + } + private string GenerateSshKeyValue() { String generateSshKeyFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".ssh"); @@ -329,8 +385,17 @@ private string GenerateSshKeyValue() { try { + // Validate if OpenSSH version is 9.4 or above to add -t rsa argument + // Which has been defaulted to ed25519 from OpenSSH 9.4 onwards + // https://github.com/openssh/openssh-portable/blob/master/ssh-keygen.c#L70 + var openSshVersion = GetOpenSSHVersion(); + // If we cannot determine the OpenSSH version, we assume it's below 9.4 to maintain compatibility. + // If openSshVersion isn't null and is >= 9.4, we add the -t rsa argument, otherwise we skip it + var keyTypeArgument = openSshVersion != null && openSshVersion >= new Version(9, 4) ? "-t rsa " : ""; process.StartInfo.FileName = "ssh-keygen"; - process.StartInfo.Arguments = String.Format("-f \"{0}\"", generateSshKeyPath); + // if keyTypeArgument is empty, we skip it to maintain compatibility with older OpenSSH versions + // Otherwise, we explicitly set the key type to rsa + process.StartInfo.Arguments = String.Format("{0}-f \"{1}\"", keyTypeArgument, generateSshKeyPath); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardInput = true; process.StartInfo.RedirectStandardError = true;