yellow team mailing list archive
-
yellow team
-
Mailing list archive
-
Message #01061
[Merge] lp:~frankban/lpsetup/remove-ssh-args into lp:lpsetup
Francesco Banconi has proposed merging lp:~frankban/lpsetup/remove-ssh-args into lp:lpsetup.
Requested reviews:
Yellow Squad (yellow)
Related bugs:
Bug #1033454 in lpsetup: "lpsetup: delete private and public key arguments and logic from inithost."
https://bugs.launchpad.net/lpsetup/+bug/1033454
For more details, see:
https://code.launchpad.net/~frankban/lpsetup/remove-ssh-args/+merge/118330
== Changes ==
Removed ssh private and public key arguments: updated subcommands and tests accordingly.
The handler *handle_ssh_keys* now just adds to namespace the names *ssh_key_path* and *valid_ssh_keys*, and raises an error if a key (of the ssh key pair) is found but not the other.
The integration test `test_install_lxc.py` pass.
--
https://code.launchpad.net/~frankban/lpsetup/remove-ssh-args/+merge/118330
Your team Yellow Squad is requested to review the proposed merge of lp:~frankban/lpsetup/remove-ssh-args into lp:lpsetup.
=== modified file 'lpsetup/handlers.py'
--- lpsetup/handlers.py 2012-07-31 09:41:24 +0000
+++ lpsetup/handlers.py 2012-08-06 10:43:18 +0000
@@ -168,72 +168,17 @@
The namespace must contain *home_dir* and *ssh_key_name*.
It should be invoked after handle_user.
- Keys contained in the namespace are escaped::
-
- >>> import argparse
- >>> private = r'PRIVATE\nKEY'
- >>> public = r'PUBLIC\nKEY'
- >>> namespace = argparse.Namespace(
- ... 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
- >>> namespace.public_key == public.decode('string-escape')
- True
- >>> 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, 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
- >>> print namespace.public_key
- None
- >>> namespace.valid_ssh_keys
- False
-
- If only one of private_key and public_key is provided, a
- ValidationError will be raised.
-
- >>> namespace = argparse.Namespace(
- ... 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...
+ This handler adds *ssh_key_path* and *valid_ssh_keys* to the namespace.
"""
- namespace.valid_ssh_keys = True
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:
- try:
- value = open(path).read()
- except IOError:
- value = None
- namespace.valid_ssh_keys = False
- setattr(namespace, attr, value)
- if bool(namespace.private_key) != bool(namespace.public_key):
- raise ValidationError(
- "arguments private-key and public-key: "
- "both must be provided or neither must be provided.")
+ namespace.valid_ssh_keys = os.path.isfile(namespace.ssh_key_path)
+ public_key_path = namespace.ssh_key_path + '.pub'
+ # Exits with an error if only one key of the ssh pair is found.
+ if namespace.valid_ssh_keys != os.path.isfile(public_key_path):
+ raise ValidationError(
+ 'ssh private and public keys {0}(.pub): both must exist '
+ 'or neither must exist.'.format(namespace.ssh_key_path))
def handle_directories(namespace):
=== modified file 'lpsetup/subcommands/inithost.py'
--- lpsetup/subcommands/inithost.py 2012-07-30 12:04:54 +0000
+++ lpsetup/subcommands/inithost.py 2012-08-06 10:43:18 +0000
@@ -99,7 +99,7 @@
os.chmod(filename, 0644)
-def setup_ssh(ssh_dir, private_key, public_key, valid_ssh_keys, ssh_key_path):
+def setup_ssh(ssh_dir, valid_ssh_keys, ssh_key_path):
"""Set up the user's `.ssh` directory.
Also ensure that SSH is configured correctly for the user.
@@ -107,28 +107,24 @@
# Set up the user's ssh directory. The ssh key must be associated
# with the lpuser's Launchpad account.
mkdirs(ssh_dir)
- # Generate user ssh keys if none are supplied.
+ # Generate user ssh keys if they don't exist.
pub_key_path = ssh_key_path + '.pub'
if not valid_ssh_keys:
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')
+ # Write the user's public key to `authorized_keys` so that the user can
+ # non-interactively ssh to the container.
+ authorized_keys = os.path.join(ssh_dir, 'authorized_keys')
+ authorized_keys_contents = open(pub_key_path).read()
+ # Add bazaar.launchpad.net to `known_hosts`.
known_hosts = os.path.join(ssh_dir, 'known_hosts')
- known_host_content = run(
+ known_host_contents = run(
'ssh-keyscan', '-t', 'rsa', 'bazaar.launchpad.net')
files_to_write = [
- (auth_file, public_key, 'a'),
- (known_hosts, known_host_content, 'a'),
+ (authorized_keys, authorized_keys_contents, 'a'),
+ (known_hosts, known_host_contents, 'a'),
]
- if valid_ssh_keys:
- # We only write out the SSH keys if we haven't had to
- # generate them further up this function.
- files_to_write.append((ssh_key_path, private_key, 'w'))
- files_to_write.append((pub_key_path, public_key, 'w'))
for filename, contents, mode in files_to_write:
write_file_contents(filename, contents, mode, header=get_file_header())
- os.chmod(ssh_key_path, 0600)
def initialize_base(user):
@@ -182,9 +178,7 @@
""".format(modules=LP_APACHE_MODULES, hosts=HOSTS_FILE)
-def setup_home(
- user, full_name, email, lpuser, private_key, public_key, valid_ssh_keys,
- ssh_key_path):
+def setup_home(user, full_name, email, lpuser, valid_ssh_keys, ssh_key_path):
"""Initialize the user home directory.
This is a separate step for several reasons::
@@ -198,9 +192,7 @@
"""
with su(user) as env:
# Set up the `.ssh` directory.
- setup_ssh(
- os.path.join(env.home, '.ssh'), private_key, public_key,
- valid_ssh_keys, ssh_key_path)
+ setup_ssh(os.path.join(env.home, '.ssh'), valid_ssh_keys, ssh_key_path)
# Set up bzr and Launchpad authentication.
call('bzr', 'whoami', formataddr([full_name, email]))
@@ -272,7 +264,7 @@
setup_home_step = (setup_home,
'user', 'full_name', 'email', 'lpuser',
- 'private_key', 'public_key', 'valid_ssh_keys', 'ssh_key_path')
+ 'valid_ssh_keys', 'ssh_key_path')
initialize_lxc_step = (initialize_lxc, )
@@ -321,20 +313,6 @@
'check out dependencies. If not provided, the system '
'user name is used.')
parser.add_argument(
- '-v', '--private-key',
- help='The SSH private key for the Launchpad user (without '
- 'passphrase). If this argument is omitted and a keypair is '
- 'not found in the home directory of the system user a new '
- 'SSH keypair will be generated and the checkout of the '
- 'Launchpad code will use HTTP rather than bzr+ssh.')
- parser.add_argument(
- '-b', '--public-key',
- help='The SSH public key for the Launchpad user. '
- 'If this argument is omitted and a keypair is not found '
- 'in the home directory of the system user a new SSH '
- 'keypair will be generated and the checkout of the '
- 'Launchpad code will use HTTP rather than bzr+ssh.')
- parser.add_argument(
'-S', '--ssh-key-name', default=SSH_KEY_NAME,
help='{0} [DEFAULT={1}]'.format(
'The ssh key name used to connect to Launchpad.',
=== modified file 'lpsetup/subcommands/initlxc.py'
--- lpsetup/subcommands/initlxc.py 2012-07-31 12:35:51 +0000
+++ lpsetup/subcommands/initlxc.py 2012-08-06 10:43:18 +0000
@@ -208,7 +208,7 @@
def inithost_in_lxc(lxc_name, ssh_key_path, user, email, full_name, lpuser,
- private_key, public_key, ssh_key_name, home_dir):
+ ssh_key_name, home_dir):
"""Prepare the Launchpad environment inside an LXC."""
# Use ssh to call this script from inside the container.
args = ['init-host', '--yes', '-u', user, '-E', email, '-f', full_name,
@@ -244,7 +244,7 @@
'lpsetup_branch')
inithost_in_lxc_step = (inithost_in_lxc,
'lxc_name', 'ssh_key_path', 'user', 'email', 'full_name', 'lpuser',
- 'private_key', 'public_key', 'ssh_key_name', 'home_dir')
+ 'ssh_key_name', 'home_dir')
stop_lxc_step = (stop_lxc,
'lxc_name', 'ssh_key_path')
=== modified file 'lpsetup/tests/subcommands/test_inithost.py'
--- lpsetup/tests/subcommands/test_inithost.py 2012-07-18 10:53:15 +0000
+++ lpsetup/tests/subcommands/test_inithost.py 2012-08-06 10:43:18 +0000
@@ -21,7 +21,7 @@
initialize_step = (inithost.initialize, ['user'])
setup_home_step = (
inithost.setup_home, ['user', 'full_name', 'email', 'lpuser',
- 'private_key', 'public_key', 'valid_ssh_keys', 'ssh_key_path',
+ 'valid_ssh_keys', 'ssh_key_path',
])
initialize_lxc_step = (inithost.initialize_lxc, [])
setup_apt_step = (inithost.setup_apt, [])
@@ -32,12 +32,9 @@
email = get_random_string()
full_name = get_random_string() + '@example.com'
lpuser = get_random_string()
- private_key = get_random_string()
- public_key = get_random_string()
ssh_key_name = get_random_string()
- return (
- '-u', user, '-E', email, '-f', full_name, '-l', lpuser,
- '-v', private_key, '-b', public_key, '-S', ssh_key_name)
+ return ('-u', user, '-E', email, '-f', full_name, '-l', lpuser,
+ '-S', ssh_key_name)
class InithostSmokeTest(StepsBasedSubCommandTestMixin, unittest.TestCase):
@@ -112,7 +109,7 @@
'/tmp/foo', 'Hello, world!', 'a+')
-class SetupSSHTestCase(unittest.TestCase):
+class SetupSSHTest(unittest.TestCase):
"""Tests for inithost.setup_ssh()."""
def setUp(self):
@@ -122,45 +119,36 @@
self.ssh_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, self.ssh_dir)
self.addCleanup(os.remove, self.temp_filename)
- self.addCleanup(os.remove, self.temp_filename + ".pub")
-
- def test_setup_ssh_writes_to_ssh_key_path(self):
- # If inithost.setup_ssh() is told that the keys it has
- # been passed are valid, it will write them out to the provided
- # ssh_key_path.
- public_key = "Public"
- private_key = "Private"
- inithost.setup_ssh(
- self.ssh_dir, private_key, public_key, True, self.temp_filename)
- with open(self.temp_filename + '.pub', 'r') as pub_file:
- self.assertIn(public_key, pub_file.read())
- with open(self.temp_filename, 'r') as priv_file:
- self.assertIn(private_key, priv_file.read())
-
- def test_setup_ssh_generates_keys_if_not_passed(self):
- # If SSH keys aren't passed to setup_ssh(), the function
- # will generate some.
- inithost.setup_ssh(
- self.ssh_dir, None, None, False, self.temp_filename)
+ self.addCleanup(os.remove, self.temp_filename + '.pub')
+
+ def test_setup_ssh_generates_keys_if_not_present(self):
+ # If SSH keys do not exist, the function will generate some.
+ inithost.setup_ssh(self.ssh_dir, False, self.temp_filename)
self.assertTrue(os.path.exists(self.temp_filename))
- self.assertTrue(os.path.exists(self.temp_filename + ".pub"))
-
- def test_setup_ssh_generates_other_files(self):
- # setup_ssh() also generates an authorized_keys file and a
- # known_hosts file in the ssh dir.
- inithost.setup_ssh(
- self.ssh_dir, None, None, False, self.temp_filename)
- self.assertTrue(
- os.path.exists(os.path.join(self.ssh_dir, 'authorized_keys')))
- self.assertTrue(
- os.path.exists(os.path.join(self.ssh_dir, 'known_hosts')))
+ self.assertTrue(os.path.exists(self.temp_filename + '.pub'))
+
+ def test_setup_ssh_generates_authorized_keys(self):
+ # setup_ssh() also generates an authorized_keys file containing
+ # the public key.
+ inithost.setup_ssh(self.ssh_dir, False, self.temp_filename)
+ authorized_keys = os.path.join(self.ssh_dir, 'authorized_keys')
+ self.assertTrue(os.path.exists(authorized_keys))
+ public_key = open(self.temp_filename + '.pub').read()
+ self.assertIn(public_key, open(authorized_keys).read())
+
+ def test_setup_ssh_generates_known_hosts(self):
+ # setup_ssh() also generates a `known_host` file containing
+ # 'bazaar.launchpad.net'.
+ inithost.setup_ssh(self.ssh_dir, False, self.temp_filename)
+ known_hosts = os.path.join(self.ssh_dir, 'known_hosts')
+ self.assertTrue(os.path.exists(known_hosts))
+ self.assertIn('bazaar.launchpad.net', open(known_hosts).read())
def test_setup_ssh_creates_ssh_dir(self):
# If the ssh_dir passed to setup_ssh() doesn't exist, it
# will be created.
shutil.rmtree(self.ssh_dir)
- inithost.setup_ssh(
- self.ssh_dir, None, None, False, self.temp_filename)
+ inithost.setup_ssh(self.ssh_dir, False, self.temp_filename)
self.assertTrue(os.path.exists(self.ssh_dir))
self.assertTrue(os.path.isdir(self.ssh_dir))
=== modified file 'lpsetup/tests/subcommands/test_initlxc.py'
--- lpsetup/tests/subcommands/test_initlxc.py 2012-07-31 09:38:14 +0000
+++ lpsetup/tests/subcommands/test_initlxc.py 2012-08-06 10:43:18 +0000
@@ -30,7 +30,7 @@
inithost_in_lxc_step = (
initlxc.inithost_in_lxc,
['lxc_name', 'ssh_key_path', 'user', 'email', 'full_name', 'lpuser',
- 'private_key', 'public_key', 'ssh_key_name', 'home_dir'])
+ 'ssh_key_name', 'home_dir'])
stop_lxc_step = (initlxc.stop_lxc, ['lxc_name', 'ssh_key_path'])
=== modified file 'lpsetup/tests/test_handlers.py'
--- lpsetup/tests/test_handlers.py 2012-07-31 09:41:24 +0000
+++ lpsetup/tests/test_handlers.py 2012-08-06 10:43:18 +0000
@@ -228,53 +228,52 @@
class HandleSSHKeysTest(HandlersTestMixin, unittest.TestCase):
- home_dir = '/tmp/__does_not_exist__'
- private = r'PRIVATE\nKEY'
- public = r'PUBLIC\nKEY'
- ssh_key_name = 'id_rsa'
+ ssh_key_name = 'my_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,
+ def setUp(self):
+ self.home_dir = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, self.home_dir)
+ self.namespace = argparse.Namespace(
ssh_key_name=self.ssh_key_name, home_dir=self.home_dir)
- handle_ssh_keys(namespace)
- self.assertEqual(
- self.private.decode('string-escape'),
- namespace.private_key)
- self.assertEqual(
- self.public.decode('string-escape'),
- namespace.public_key)
- self.assertTrue(namespace.valid_ssh_keys)
+
+ def get_ssh_key_path(self):
+ return os.path.join(self.home_dir, '.ssh', self.ssh_key_name)
+
+ def write_key(self, path):
+ directory = os.path.dirname(path)
+ if not os.path.exists(directory):
+ os.mkdir(os.path.dirname(path))
+ with open(path, 'w') as f:
+ f.write('KEY CONTENTS')
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, 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)
- self.assertFalse(namespace.valid_ssh_keys)
+ handle_ssh_keys(self.namespace)
+ self.assertEqual(self.get_ssh_key_path(), self.namespace.ssh_key_path)
+
+ def test_invalid_keys(self):
+ # Keys are marked as invalid if they can not be found in the
+ # current home directory.
+ handle_ssh_keys(self.namespace)
+ self.assertFalse(self.namespace.valid_ssh_keys)
+
+ def test_valid_keys(self):
+ # Keys are marked as valid if they are correctly found in the
+ # current home directory.
+ ssh_key_path = self.get_ssh_key_path()
+ self.write_key(ssh_key_path)
+ self.write_key(ssh_key_path + '.pub')
+ handle_ssh_keys(self.namespace)
+ self.assertTrue(self.namespace.valid_ssh_keys)
def test_only_one_key(self):
- # Ensure a `ValidationError` is raised if only one key is provided.
- namespace = argparse.Namespace(
- private_key=self.private, public_key=None,
- ssh_key_name=self.ssh_key_name, home_dir=self.home_dir)
- with self.assertNotValid('private-key'):
- handle_ssh_keys(namespace)
+ # Ensure the handler raises a ValidationError if only one key
+ # of the private/public pair is found.
+ ssh_key_path = self.get_ssh_key_path()
+ self.write_key(ssh_key_path)
+ with self.assertNotValid(ssh_key_path):
+ handle_ssh_keys(self.namespace)
class HandleTargetFromRepositoryTest(unittest.TestCase):