Repository: Azure/WALinuxAgent
Pull request #786 (2017-06-23 13:58:17)

Add Provisioning.SshHostKeyPairType=auto to support ssh-keygen -A
The supported values are now `rsa`, `dsa`, `ecdsa`, `ed25519`, or **`auto`**.  **`auto`** generates all supported host key types in the correct directory by calling `ssh-keygen -A` (supported since ***Open*****SSH** 5.9 in 2011).  The code uses `rsa` for the thumbprint because only a single one is supported by the agent and the Azure protocol.


Patch:
diff --git a/azurelinuxagent/common/conf.py b/azurelinuxagent/common/conf.py
index 5422784b..f0c3a9a1 100644
--- a/azurelinuxagent/common/conf.py
+++ b/azurelinuxagent/common/conf.py
@@ -151,6 +151,16 @@ def get_root_device_scsi_timeout(conf=__conf__):
     return conf.get("OS.RootDeviceScsiTimeout", None)
 
 def get_ssh_host_keypair_type(conf=__conf__):
+    keypair_type = conf.get("Provisioning.SshHostKeyPairType", "rsa")
+    if keypair_type == "auto":
+        '''
+        auto generates all supported key types and returns the
+        rsa thumbprint as the default.
+        '''
+        return "rsa"
+    return keypair_type
+
+def get_ssh_host_keypair_mode(conf=__conf__):
     return conf.get("Provisioning.SshHostKeyPairType", "rsa")
 
 def get_provision_enabled(conf=__conf__):
diff --git a/azurelinuxagent/common/version.py b/azurelinuxagent/common/version.py
index d1d4c625..e7ca4524 100644
--- a/azurelinuxagent/common/version.py
+++ b/azurelinuxagent/common/version.py
@@ -113,7 +113,7 @@ def get_distro():
 
 AGENT_NAME = "WALinuxAgent"
 AGENT_LONG_NAME = "Azure Linux Agent"
-AGENT_VERSION = '2.2.14'
+AGENT_VERSION = '2.2.13.1'
 AGENT_LONG_VERSION = "{0}-{1}".format(AGENT_NAME, AGENT_VERSION)
 AGENT_DESCRIPTION = """
 The Azure Linux Agent supports the provisioning and running of Linux
diff --git a/azurelinuxagent/pa/deprovision/default.py b/azurelinuxagent/pa/deprovision/default.py
index e2c56132..bdd108f4 100644
--- a/azurelinuxagent/pa/deprovision/default.py
+++ b/azurelinuxagent/pa/deprovision/default.py
@@ -155,28 +155,23 @@ class DeprovisionHandler(object):
             ]
         return dirs
     
-    def cloud_init_files(self, include_once=True, deluser=False):
-        files = []
-        if deluser:
-            files += [
-                "/etc/sudoers.d/90-cloud-init-users"
-            ]
+    def cloud_init_files(self, include_once=True):
+        files = [
+            "/etc/sudoers.d/90-cloud-init-users"
+        ]
         if include_once:
             files += [
                 "/var/lib/cloud/sem/config_scripts_per_once.once"
             ]
         return files
 
-    def del_cloud_init(self, warnings, actions,
-            include_once=True, deluser=False):
+    def del_cloud_init(self, warnings, actions, include_once=True):
         dirs = [d for d in self.cloud_init_dirs(include_once=include_once) \
                     if os.path.isdir(d)]
         if len(dirs) > 0:
             actions.append(DeprovisionAction(fileutil.rm_dirs, dirs))
 
-        files = [f for f in self.cloud_init_files(
-                                    include_once=include_once,
-                                    deluser=deluser) \
+        files = [f for f in self.cloud_init_files(include_once=include_once) \
                     if os.path.isfile(f)]
         if len(files) > 0:
             actions.append(DeprovisionAction(fileutil.rm_files, files))
@@ -202,7 +197,7 @@ class DeprovisionHandler(object):
         if conf.get_delete_root_password():
             self.del_root_password(warnings, actions)
 
-        self.del_cloud_init(warnings, actions, deluser=deluser)
+        self.del_cloud_init(warnings, actions)
         self.del_dirs(warnings, actions)
         self.del_files(warnings, actions)
         self.del_resolv(warnings, actions)
@@ -216,8 +211,7 @@ class DeprovisionHandler(object):
         warnings = []
         actions = []
 
-        self.del_cloud_init(warnings, actions,
-                    include_once=False, deluser=False)
+        self.del_cloud_init(warnings, actions, include_once=False)
         self.del_dhcp_lease(warnings, actions)
         self.del_lib_dir_files(warnings, actions)
 
diff --git a/azurelinuxagent/pa/provision/default.py b/azurelinuxagent/pa/provision/default.py
index 959a2fe7..5705f1b7 100644
--- a/azurelinuxagent/pa/provision/default.py
+++ b/azurelinuxagent/pa/provision/default.py
@@ -128,9 +128,17 @@ class ProvisionHandler(object):
         keypair_type = conf.get_ssh_host_keypair_type()
         if conf.get_regenerate_ssh_host_key():
             fileutil.rm_files(conf.get_ssh_key_glob())
-            keygen_cmd = "ssh-keygen -N '' -t {0} -f {1}"
-            shellutil.run(keygen_cmd.format(keypair_type,
-                        conf.get_ssh_key_private_path()))
+            if conf.get_ssh_host_keypair_mode() == "auto":
+                '''
+                The -A option generates all supported key types.
+                This is supported since OpenSSH 5.9 (2011).
+                '''
+                shellutil.run("ssh-keygen -A")
+            else:
+                keygen_cmd = "ssh-keygen -N '' -t {0} -f {1}"
+                shellutil.run(keygen_cmd.
+                              format(keypair_type,
+                                     conf.get_ssh_key_private_path()))
         return self.get_ssh_host_key_thumbprint()
 
     def get_ssh_host_key_thumbprint(self, chk_err=True):
diff --git a/config/openbsd/waagent.conf b/config/openbsd/waagent.conf
index 09e7db74..ec47a786 100644
--- a/config/openbsd/waagent.conf
+++ b/config/openbsd/waagent.conf
@@ -14,8 +14,8 @@ Provisioning.DeleteRootPassword=y
 # Generate fresh host key pair.
 Provisioning.RegenerateSshHostKeyPair=y
 
-# Supported values are "rsa", "dsa", "ecdsa", and "ed25519".
-Provisioning.SshHostKeyPairType=ed25519
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
+Provisioning.SshHostKeyPairType=auto
 
 # Monitor host name changes and publish changes via DHCP requests.
 Provisioning.MonitorHostName=y
diff --git a/config/waagent.conf b/config/waagent.conf
index b1b1ba3f..59a47787 100644
--- a/config/waagent.conf
+++ b/config/waagent.conf
@@ -14,7 +14,7 @@ Provisioning.DeleteRootPassword=y
 # Generate fresh host key pair.
 Provisioning.RegenerateSshHostKeyPair=y
 
-# Supported values are "rsa", "dsa" and "ecdsa".
+# Supported values are "rsa", "dsa", "ecdsa", "ed25519", and "auto".
 Provisioning.SshHostKeyPairType=rsa
 
 # Monitor host name changes and publish changes via DHCP requests.


Test patch:
diff --git a/tests/pa/test_deprovision.py b/tests/pa/test_deprovision.py
index b2f7f0c0..24456786 100644
--- a/tests/pa/test_deprovision.py
+++ b/tests/pa/test_deprovision.py
@@ -59,11 +59,10 @@ class TestDeprovision(AgentTestCase):
                 mock_files,
                 mock_dirs):
         deprovision_handler = get_deprovision_handler("","","")
-        deprovision_handler.del_cloud_init([], [],
-                            include_once=False, deluser=False)
+        deprovision_handler.del_cloud_init([], [], include_once=False)
 
         mock_dirs.assert_called_with(include_once=False)
-        mock_files.assert_called_with(include_once=False, deluser=False)
+        mock_files.assert_called_with(include_once=False)
 
     @patch("signal.signal")
     @patch("azurelinuxagent.common.protocol.get_protocol_util")
@@ -88,11 +87,10 @@ class TestDeprovision(AgentTestCase):
                 mock_files.return_value = files
 
                 deprovision_handler = get_deprovision_handler("","","")
-                deprovision_handler.del_cloud_init(warnings, actions,
-                        deluser=True)
+                deprovision_handler.del_cloud_init(warnings, actions)
 
                 mock_dirs.assert_called_with(include_once=True)
-                mock_files.assert_called_with(include_once=True, deluser=True)
+                mock_files.assert_called_with(include_once=True)
 
                 self.assertEqual(len(warnings), 0)
                 self.assertEqual(len(actions), 2)