← Back to team overview

yellow team mailing list archive

[Merge] lp:~frankban/lpsetup/ssh-keys into lp:lpsetup

 

Francesco Banconi has proposed merging lp:~frankban/lpsetup/ssh-keys into lp:lpsetup.

Requested reviews:
  Launchpad Yellow Squad (yellow)

For more details, see:
https://code.launchpad.net/~frankban/lpsetup/ssh-keys/+merge/98175

== Changes ==

- Updated the install and lxc-install subcommans to support a custom ssh key name.
- The root ssh key is no longer needed, so it is not created.
-- 
https://code.launchpad.net/~frankban/lpsetup/ssh-keys/+merge/98175
Your team Launchpad Yellow Squad is requested to review the proposed merge of lp:~frankban/lpsetup/ssh-keys into lp:lpsetup.
=== modified file 'lpsetup/handlers.py'
--- lpsetup/handlers.py	2012-03-16 10:27:22 +0000
+++ lpsetup/handlers.py	2012-03-19 10:05:30 +0000
@@ -139,7 +139,8 @@
         >>> private = r'PRIVATE\nKEY'
         >>> public = r'PUBLIC\nKEY'
         >>> namespace = argparse.Namespace(
-        ...     private_key=private, public_key=public)
+        ...     private_key=private, public_key=public,
+        ...     ssh_key_name='id_rsa', home_dir='/tmp/')
         >>> handle_ssh_keys(namespace)
         >>> namespace.private_key == private.decode('string-escape')
         True
@@ -148,11 +149,18 @@
         >>> namespace.valid_ssh_keys
         True
 
+    After this handler is called, the ssh key path is present as an attribute
+    of the namespace::
+
+        >>> namespace.ssh_key_path
+        '/tmp/.ssh/id_rsa'
+
     Keys are None if they are not provided and can not be found in the
     current home directory::
 
         >>> namespace = argparse.Namespace(
-        ...     private_key=None, home_dir='/tmp/__does_not_exist__')
+        ...     private_key=None, public_key=None, ssh_key_name='id_rsa',
+        ...     home_dir='/tmp/__does_not_exists__')
         >>> handle_ssh_keys(namespace) # doctest: +ELLIPSIS
         >>> print namespace.private_key
         None
@@ -165,21 +173,22 @@
     ValidationError will be raised.
 
         >>> namespace = argparse.Namespace(
-        ...     private_key=private, public_key=None,
-        ...     home_dir='/tmp/__does_not_exist__')
+        ...     private_key=private, public_key=None, ssh_key_name='id_rsa',
+        ...     home_dir='/tmp/__does_not_exists__')
         >>> handle_ssh_keys(namespace) # doctest: +ELLIPSIS
         Traceback (most recent call last):
         ValidationError: arguments private-key...
     """
     namespace.valid_ssh_keys = True
-    for attr, filename in (
-        ('private_key', 'id_rsa'),
-        ('public_key', 'id_rsa.pub')):
+    namespace.ssh_key_path = os.path.join(
+        namespace.home_dir, '.ssh', namespace.ssh_key_name)
+    for attr, path in (
+        ('private_key', namespace.ssh_key_path),
+        ('public_key', namespace.ssh_key_path + '.pub')):
         value = getattr(namespace, attr, None)
         if value:
             setattr(namespace, attr, value.decode('string-escape'))
         else:
-            path = os.path.join(namespace.home_dir, '.ssh', filename)
             try:
                 value = open(path).read()
             except IOError:

=== modified file 'lpsetup/settings.py'
--- lpsetup/settings.py	2012-03-16 14:49:20 +0000
+++ lpsetup/settings.py	2012-03-19 10:05:30 +0000
@@ -69,3 +69,4 @@
 LXC_PACKAGES = ['lxc', 'libvirt-bin']
 LXC_PATH = '/var/lib/lxc/'
 RESOLV_FILE = '/etc/resolv.conf'
+SSH_KEY_NAME = 'id_rsa'

=== modified file 'lpsetup/subcommands/install.py'
--- lpsetup/subcommands/install.py	2012-03-16 14:49:20 +0000
+++ lpsetup/subcommands/install.py	2012-03-19 10:05:30 +0000
@@ -50,6 +50,7 @@
     LP_PACKAGES,
     LP_REPOS,
     LP_SOURCE_DEPS,
+    SSH_KEY_NAME,
     )
 from lpsetup.utils import call
 
@@ -111,10 +112,9 @@
         call('make', '-C', checkout_dir, 'install')
 
 
-
 def initialize(
     user, full_name, email, lpuser, private_key, public_key, valid_ssh_keys,
-    dependencies_dir, directory):
+    ssh_key_path, dependencies_dir, directory):
     """Initialize host machine."""
     # Install necessary deb packages.  This requires Oneiric or later.
     call('apt-get', 'update')
@@ -122,35 +122,31 @@
     # Create the user (if he does not exist).
     if not user_exists(user):
         call('useradd', '-m', '-s', '/bin/bash', '-U', user)
-    # Generate root ssh keys if they do not exist.
-    if not os.path.exists('/root/.ssh/id_rsa.pub'):
-        generate_ssh_keys('/root/.ssh/')
     with su(user) as env:
         # Set up the user's ssh directory.  The ssh key must be associated
         # with the lpuser's Launchpad account.
         ssh_dir = os.path.join(env.home, '.ssh')
         mkdirs(ssh_dir)
         # Generate user ssh keys if none are supplied.
+        pub_key_path = ssh_key_path + '.pub'
         if not valid_ssh_keys:
-            generate_ssh_keys(ssh_dir)
-            private_key = open(os.path.join(ssh_dir, 'id_rsa')).read()
-            public_key = open(os.path.join(ssh_dir, 'id_rsa.pub')).read()
-        priv_file = os.path.join(ssh_dir, 'id_rsa')
-        pub_file = os.path.join(ssh_dir, 'id_rsa.pub')
+            generate_ssh_keys(ssh_key_path)
+            private_key = open(ssh_key_path).read()
+            public_key = open(pub_key_path).read()
         auth_file = os.path.join(ssh_dir, 'authorized_keys')
         known_hosts = os.path.join(ssh_dir, 'known_hosts')
-        known_host_content = run(
-            'ssh-keyscan', '-t', 'rsa', 'bazaar.launchpad.net')
+        known_host_content = subprocess.check_output([
+            'ssh-keyscan', '-t', 'rsa', 'bazaar.launchpad.net'])
         for filename, contents, mode in [
-            (priv_file, private_key, 'w'),
-            (pub_file, public_key, 'w'),
+            (ssh_key_path, private_key, 'w'),
+            (pub_key_path, public_key, 'w'),
             (auth_file, public_key, 'a'),
             (known_hosts, known_host_content, 'a'),
             ]:
             with open(filename, mode) as f:
                 f.write(contents + '\n')
             os.chmod(filename, 0644)
-        os.chmod(priv_file, 0600)
+        os.chmod(ssh_key_path, 0600)
         # Set up bzr and Launchpad authentication.
         call('bzr', 'whoami', formataddr([full_name, email]))
         if valid_ssh_keys:
@@ -220,14 +216,17 @@
 
     actions = (
         (initialize,
-         'user', 'full_name', 'email', 'lpuser', 'private_key',
-         'public_key', 'valid_ssh_keys', 'dependencies_dir', 'directory'),
-        (setup_apt, 'no_repositories'),
+         'user', 'full_name', 'email', 'lpuser',
+         'private_key', 'public_key', 'valid_ssh_keys', 'ssh_key_path',
+         'dependencies_dir', 'directory'),
+        (setup_apt,
+         'no_repositories'),
         (setup_launchpad,
          'user', 'dependencies_dir', 'directory', 'valid_ssh_keys'),
         )
     help = __doc__
     needs_root = True
+    ssh_key_name_help = 'The ssh key name used to connect to Launchpad.'
     validators = (
         handlers.handle_user,
         handlers.handle_lpuser,
@@ -284,5 +283,9 @@
                  'given user (see -u argument). '
                  '[DEFAULT={0}]'.format(CHECKOUT_DIR))
         parser.add_argument(
+            '-s', '--ssh-key-name', default=SSH_KEY_NAME,
+            help='{0} [DEFAULT={1}]'.format(
+                self.ssh_key_name_help, SSH_KEY_NAME))
+        parser.add_argument(
             '-N', '--no-repositories', action='store_true',
             help='Do not add APT repositories.')

=== modified file 'lpsetup/subcommands/lxcinstall.py'
--- lpsetup/subcommands/lxcinstall.py	2012-03-09 10:14:32 +0000
+++ lpsetup/subcommands/lxcinstall.py	2012-03-19 10:05:30 +0000
@@ -91,8 +91,6 @@
     # Set up root ssh key.
     user_authorized_keys = os.path.expanduser(
         '~' + user + '/.ssh/authorized_keys')
-    with open(user_authorized_keys, 'a') as f:
-        f.write(open('/root/.ssh/id_rsa.pub').read())
     dst = get_container_path(lxc_name, '/root/.ssh/')
     mkdirs(dst)
     shutil.copy(user_authorized_keys, dst)
@@ -103,9 +101,9 @@
     call('lxc-start', '-n', lxc_name, '-d')
 
 
-def wait_for_lxc(lxc_name, trials=60, sleep_seconds=1):
+def wait_for_lxc(lxc_name, ssh_key_path, trials=60, sleep_seconds=1):
     """Try to ssh as `user` into the LXC container named `lxc_name`."""
-    sshcall = ssh(lxc_name)
+    sshcall = ssh(lxc_name, key=ssh_key_path)
     while True:
         trials -= 1
         try:
@@ -118,19 +116,20 @@
             break
 
 
-def initialize_lxc(lxc_name, lxc_os):
+def initialize_lxc(lxc_name, lxc_os, ssh_key_path):
     """Initialize LXC container."""
     base_packages = list(BASE_PACKAGES)
     if lxc_os == 'lucid':
         # Install argparse to be able to run this script inside a lucid lxc.
         base_packages.append('python-argparse')
-    ssh(lxc_name)(
+    sshcall = ssh(lxc_name, key=ssh_key_path)
+    sshcall(
         'DEBIAN_FRONTEND=noninteractive '
         'apt-get install -y ' + ' '.join(base_packages))
 
 
 def setup_launchpad_lxc(
-    user, dependencies_dir, directory, valid_ssh_keys, lxc_name):
+    user, dependencies_dir, directory, valid_ssh_keys, ssh_key_path, lxc_name):
     """Set up the Launchpad environment inside an LXC."""
     # Use ssh to call this script from inside the container.
     args = [
@@ -138,12 +137,12 @@
         '-d', dependencies_dir, '-c', directory
         ]
     cmd = this_command(directory, args)
-    ssh(lxc_name)(cmd)
-
-
-def stop_lxc(lxc_name):
+    ssh(lxc_name, key=ssh_key_path)(cmd)
+
+
+def stop_lxc(lxc_name, ssh_key_path):
     """Stop the lxc instance named `lxc_name`."""
-    ssh(lxc_name)('poweroff')
+    ssh(lxc_name, key=ssh_key_path)('poweroff')
     if not lxc_stopped(lxc_name):
         subprocess.call(['lxc-stop', '-n', lxc_name])
 
@@ -157,16 +156,21 @@
          'public_key', 'valid_ssh_keys', 'dependencies_dir', 'directory'),
         (create_lxc,
          'user', 'lxc_name', 'lxc_arch', 'lxc_os'),
-        (start_lxc, 'lxc_name'),
-        (wait_for_lxc, 'lxc_name'),
+        (start_lxc,
+         'lxc_name'),
+        (wait_for_lxc,
+         'lxc_name', 'ssh_key_path'),
         (initialize_lxc,
-         'lxc_name', 'lxc_os'),
+         'lxc_name', 'lxc_os', 'ssh_key_path'),
         (setup_launchpad_lxc,
-         'user', 'dependencies_dir', 'directory', 'valid_ssh_keys',
-         'lxc_name'),
-        (stop_lxc, 'lxc_name'),
+         'user', 'dependencies_dir', 'directory',
+         'valid_ssh_keys', 'ssh_key_path', 'lxc_name'),
+        (stop_lxc,
+         'lxc_name', 'ssh_key_path'),
         )
     help = __doc__
+    ssh_key_name_help = ('The ssh key name used to connect to Launchpad '
+                         'and to to the LXC container.')
 
     def add_arguments(self, parser):
         super(SubCommand, self).add_arguments(parser)

=== modified file 'lpsetup/subcommands/update.py'
--- lpsetup/subcommands/update.py	2012-03-09 10:14:32 +0000
+++ lpsetup/subcommands/update.py	2012-03-19 10:05:30 +0000
@@ -56,7 +56,6 @@
                 call(cmd, '--parent', dependencies_dir, '--target', branch)
 
 
-
 class SubCommand(argparser.ActionsBasedSubCommand):
     """Update the Launchpad environment to latest version."""
 
@@ -64,7 +63,7 @@
         (update_launchpad,
          'user', 'valid_ssh_keys', 'dependencies_dir', 'directory', 'apt'),
         (link_sourcecode_in_branches,
-            'user', 'dependencies_dir', 'directory'),
+         'user', 'dependencies_dir', 'directory'),
         )
     help = __doc__
     validators = (

=== modified file 'lpsetup/tests/test_handlers.py'
--- lpsetup/tests/test_handlers.py	2012-03-16 14:06:12 +0000
+++ lpsetup/tests/test_handlers.py	2012-03-19 10:05:30 +0000
@@ -101,13 +101,16 @@
 
 class HandleSSHKeysTest(HandlersTestMixin, unittest.TestCase):
 
+    home_dir = '/tmp/__does_not_exist__'
     private = r'PRIVATE\nKEY'
     public = r'PUBLIC\nKEY'
+    ssh_key_name = 'id_rsa'
 
     def test_key_escaping(self):
         # Ensure the keys contained in the namespace are correctly escaped.
         namespace = argparse.Namespace(
-            private_key=self.private, public_key=self.public)
+            private_key=self.private, public_key=self.public,
+            ssh_key_name=self.ssh_key_name, home_dir=self.home_dir)
         handle_ssh_keys(namespace)
         self.assertEqual(
             self.private.decode('string-escape'),
@@ -117,11 +120,22 @@
             namespace.public_key)
         self.assertTrue(namespace.valid_ssh_keys)
 
+    def test_ssh_key_path_in_namespace(self):
+        # After the handler is called, the ssh key path is present
+        # as an attribute of the namespace.
+        namespace = argparse.Namespace(
+            private_key=self.private, public_key=self.public,
+            ssh_key_name=self.ssh_key_name, home_dir=self.home_dir)
+        handle_ssh_keys(namespace)
+        expected = self.home_dir + '/.ssh/id_rsa'
+        self.assertEqual(expected, namespace.ssh_key_path)
+
     def test_no_keys(self):
         # Keys are None if they are not provided and can not be found in the
         # current home directory.
         namespace = argparse.Namespace(
-            private_key=None, home_dir='/tmp/__does_not_exist__')
+            private_key=None, ssh_key_name=self.ssh_key_name,
+            home_dir=self.home_dir)
         handle_ssh_keys(namespace)
         self.assertIsNone(namespace.private_key)
         self.assertIsNone(namespace.public_key)
@@ -131,7 +145,7 @@
         # Ensure a `ValidationError` is raised if only one key is provided.
         namespace = argparse.Namespace(
             private_key=self.private, public_key=None,
-            home_dir='/tmp/__does_not_exist__')
+            ssh_key_name=self.ssh_key_name, home_dir=self.home_dir)
         with self.assertNotValid('private-key'):
             handle_ssh_keys(namespace)