From ed9b1a9a9e9abf54e472058d0da8cfc70e025cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vieira?= Date: Thu, 18 Dec 2025 16:52:20 -0800 Subject: [PATCH 1/4] Enforce RSA as the default key type --- src/Aks/Aks/Commands/NewAzureRmAks.cs | 64 ++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/src/Aks/Aks/Commands/NewAzureRmAks.cs b/src/Aks/Aks/Commands/NewAzureRmAks.cs index 0dc55086dcbc..c1b0ed3c0056 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,58 @@ private void PreValidate() } } + // From OpenSSH 9.4 onwards, the default key type is ed25519, which is not supported in AKS. + // To ensure retrocompatibility, 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); + process.StartInfo.FileName = isWindows ? "C:\\Windows\\System32\\OpenSSH\\ssh.exe" : "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 standOutput = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); + + process.WaitForExit(); + // OpenSSH version for Windows follows the format: OpenSSH_for_Windows_X.XpX + // Examples + // "OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.3" + // "OpenSSH_for_Windows_9.5p1, LibreSSL 3.8.2" + var regMatch = System.Text.RegularExpressions.Regex.Match(standOutput, @"OpenSSH_for_Windows_(\d+)\.(\d+)p(\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 +382,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.IsNullOrEmpty(keyTypeArgument) ? String.Format("-f \"{0}\"", generateSshKeyPath) : String.Format("-t rsa -f \"{0}\"", generateSshKeyPath); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardInput = true; process.StartInfo.RedirectStandardError = true; From d6afc580b83c0b1992ff2040de7f85d4e0b2f558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vieira?= <33453532+jovieir@users.noreply.github.com> Date: Thu, 18 Dec 2025 18:19:39 -0800 Subject: [PATCH 2/4] Update src/Aks/Aks/Commands/NewAzureRmAks.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Aks/Aks/Commands/NewAzureRmAks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Aks/Aks/Commands/NewAzureRmAks.cs b/src/Aks/Aks/Commands/NewAzureRmAks.cs index c1b0ed3c0056..5c64bf09c41e 100644 --- a/src/Aks/Aks/Commands/NewAzureRmAks.cs +++ b/src/Aks/Aks/Commands/NewAzureRmAks.cs @@ -331,14 +331,14 @@ static Version GetOpenSSHVersion() process.StartInfo.CreateNoWindow = true; process.Start(); - string standOutput = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); + string standardOutput = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); process.WaitForExit(); // OpenSSH version for Windows follows the format: OpenSSH_for_Windows_X.XpX // Examples // "OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.3" // "OpenSSH_for_Windows_9.5p1, LibreSSL 3.8.2" - var regMatch = System.Text.RegularExpressions.Regex.Match(standOutput, @"OpenSSH_for_Windows_(\d+)\.(\d+)p(\d+)"); + var regMatch = System.Text.RegularExpressions.Regex.Match(standardOutput, @"OpenSSH_for_Windows_(\d+)\.(\d+)p(\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; From e3a7acaed8496c64454a6e0beb2fe2a59ba023ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vieira?= <33453532+jovieir@users.noreply.github.com> Date: Thu, 18 Dec 2025 20:58:31 -0800 Subject: [PATCH 3/4] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Aks/Aks/Commands/NewAzureRmAks.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Aks/Aks/Commands/NewAzureRmAks.cs b/src/Aks/Aks/Commands/NewAzureRmAks.cs index 5c64bf09c41e..4ff050a78ef3 100644 --- a/src/Aks/Aks/Commands/NewAzureRmAks.cs +++ b/src/Aks/Aks/Commands/NewAzureRmAks.cs @@ -313,7 +313,7 @@ private void PreValidate() } // From OpenSSH 9.4 onwards, the default key type is ed25519, which is not supported in AKS. - // To ensure retrocompatibility, we need to check the OpenSSH version installed and adjust the parameters accordingly. + // 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()) @@ -322,7 +322,8 @@ static Version GetOpenSSHVersion() { // Using runtime information to determine the path to ssh.exe based on OS type. bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - process.StartInfo.FileName = isWindows ? "C:\\Windows\\System32\\OpenSSH\\ssh.exe" : "ssh"; + 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; @@ -392,7 +393,7 @@ private string GenerateSshKeyValue() process.StartInfo.FileName = "ssh-keygen"; // 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.IsNullOrEmpty(keyTypeArgument) ? String.Format("-f \"{0}\"", generateSshKeyPath) : String.Format("-t rsa -f \"{0}\"", generateSshKeyPath); + process.StartInfo.Arguments = String.Format("{0}-f \"{1}\"", keyTypeArgument, generateSshKeyPath); process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardInput = true; process.StartInfo.RedirectStandardError = true; From 699e49c9ba9aec452b3992ac2609c28e3803fb19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Vieira?= <33453532+jovieir@users.noreply.github.com> Date: Thu, 18 Dec 2025 23:29:02 -0800 Subject: [PATCH 4/4] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Aks/Aks/Commands/NewAzureRmAks.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Aks/Aks/Commands/NewAzureRmAks.cs b/src/Aks/Aks/Commands/NewAzureRmAks.cs index 4ff050a78ef3..c97d16da0275 100644 --- a/src/Aks/Aks/Commands/NewAzureRmAks.cs +++ b/src/Aks/Aks/Commands/NewAzureRmAks.cs @@ -335,15 +335,17 @@ static Version GetOpenSSHVersion() string standardOutput = process.StandardOutput.ReadToEnd() + process.StandardError.ReadToEnd(); process.WaitForExit(); - // OpenSSH version for Windows follows the format: OpenSSH_for_Windows_X.XpX - // Examples - // "OpenSSH_for_Windows_8.6p1, LibreSSL 3.4.3" - // "OpenSSH_for_Windows_9.5p1, LibreSSL 3.8.2" - var regMatch = System.Text.RegularExpressions.Regex.Match(standardOutput, @"OpenSSH_for_Windows_(\d+)\.(\d+)p(\d+)"); + // 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. + } + // We're not expecting ssh to be missing, but just in case, we catch any exception and return null. catch { return null;