Skip to content

Addressing -ssh-key-gen bug.#7105

Merged
williexu merged 6 commits intoAzure:devfrom
adewaleo:ova-GenSSHKeysVMUnsafe
Sep 6, 2018
Merged

Addressing -ssh-key-gen bug.#7105
williexu merged 6 commits intoAzure:devfrom
adewaleo:ova-GenSSHKeysVMUnsafe

Conversation

@adewaleo
Copy link
Contributor

@adewaleo adewaleo commented Aug 21, 2018

Fixes #4725 and #6780 by using id_rsa as the private key file (if it exists) to generate a new public key instead of generating a new private-public key pair (which overwrites id-rsa).

I tested this fix by deleting my public key and running az vm create ... --ssh-key-gen.

A new public key was generated in id_rsa.pub and I was able to ssh into the newly created VM. I was able to create another vm using the existing keys pair and ssh into it.

Please let me know your thoughts on this PR.


This checklist is used to make sure that common guidelines for a pull request are followed.

  • The PR has modified HISTORY.rst describing any customer-facing, functional changes. Note that this does not include changes only to help content. (see Modifying change log).

  • I adhere to the Command Guidelines.

Copy link
Contributor

@williexu williexu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments

content = string_or_file
if os.path.exists(string_or_file):
logger.info('Use existing SSH public key file: %s', string_or_file)
logger.info('Reusing existing SSH public key from %s', string_or_file)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave the original logging msg, at this point, the CLI only knows the file exists and the key has not been verified yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I will leave the original logging message.

@adewaleo adewaleo force-pushed the ova-GenSSHKeysVMUnsafe branch from 8a4968f to ebc2b43 Compare August 23, 2018 18:36
Copy link
Member

@tjprescott tjprescott left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@williexu williexu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some comments, also check CI

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You only want to catch a FileNotFoundError right?
It may be better to do the existence check like you do with the public key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I will change this to check the path instead

@adewaleo
Copy link
Contributor Author

I made the requested changes, can someone please look at the version numbers and make sure they look ok

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use the absolute namespace.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use str.format()

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this in the tearDown otherwise, it is not executed when the test fails.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

str.format

@adewaleo
Copy link
Contributor Author

So is it okay to merge this?

@troydai
Copy link
Contributor

troydai commented Aug 28, 2018

Please address my comments.

@williexu
Copy link
Contributor

williexu commented Sep 4, 2018

@adewaleo you might need to rebase your branch, check the changes for the PR

Oluwatosin Adewale added 2 commits September 4, 2018 14:00
…" is run and id_rsa exists but id_rsa.pub does not exist.
@adewaleo adewaleo force-pushed the ova-GenSSHKeysVMUnsafe branch from 38b88b6 to 7c67fdb Compare September 4, 2018 21:38
public_key_filepath, pub_ssh_dir)

return public_key
except IOError:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileNotFoundError, IOError spans too many scenarios

Copy link
Contributor Author

@adewaleo adewaleo Sep 4, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately FileNotFoundError is not defined in Python 2. IOError handles the case that the file isn't found and that the file is unreadable for some other reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if I should check if the file exists and handle the IOError separately?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You want different behaviors for a non-existing public file vs other unreadable file errors(no permission, password-protected, etc) correct?

In that case, you need to differentiate between the two; if FileNotFound is not available in Python2, the best option seems to simply check for existence.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have addressed your comments, thanks for the feedback.

try:
with open(public_key_filepath, 'r') as public_key_file:
public_key = public_key_file.read()
pub_ssh_dir, _ = os.path.split(public_key_filepath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the basename is not used here just use os.path.dirname

https://docs.python.org/3/library/os.path.html?highlight=os%20path%20dirname#os.path.dirname

key = paramiko.RSAKey(filename=private_key_filepath)
logger.warning("Private SSH key file '%s' found in dir '%s'."
" Generating public key file '%s'",
private_key_filepath, ssh_dir, public_key_filepath)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the fallback here when it fails to load the private key?

cls.key.write_private_key(keyOutput)

cls.private_key = keyOutput.getvalue()
cls.public_key = '{} {}'.format(cls.key.get_name(), cls.key.get_base64())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just leave it in the setUp?

if os.path.isfile(fp):
os.remove(fp)
else:
shutil.rmtree(fp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Way too complicated. One setUp tearDown pair and execute the same logic for each test method. There is no need to share the construction between test methods.

self.assertTrue(os.path.isfile(public_key_file4))

# delete temporary directory and its files
shutil.rmtree(temp_dir_name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I have addressed your comments, thanks for the feedback.

@troydai troydai added this to the Sprint 45 - Ignite milestone Sep 4, 2018
Copy link
Contributor

@yugangw-msft yugangw-msft left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall. Thanks for the change! I added a few comments, hope they help you remove some code.

import paramiko
from paramiko.ssh_exception import PasswordRequiredException, SSHException

if os.path.isfile(public_key_filepath):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don't need this as the caller is supposed to check the public key file not existing and then invoke this. Check out the code here

Copy link
Contributor Author

@adewaleo adewaleo Sep 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hear you Yugang, but I was asked by @williexu to update this method independent of the current caller's behavior. I imagine this is to make sure the method doesn't fail if a future caller of this method fails to make the check.

Copy link
Contributor

@yugangw-msft yugangw-msft Sep 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with this idea, but then you also need to remove the redundant file check in VM and ACS modules. I thought we can do step by step through the next PR as this piece is pretty important so risk is a concern, but up to you.

++++++
* Minor fixes.
* Fixed issue where `az vm create --generate-ssh-keys` overwrites private key
file if public key file is missing. (#4725, #6790)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be #6780

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch! Will update!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, thanks

def _create_new_temp_key_file(self, key_data, suffix=""):
with tempfile.NamedTemporaryFile(mode='w', dir=self._tempdirName, delete=False, suffix=suffix) as f:
f.write(key_data)
file_path = f.name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can just do return f.name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will do.

# delete temporary directory to be used for temp files.
shutil.rmtree(self._tempdirName)

def test_public_key_file_exists(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest name like test_when_public_key_file_exists? also this test can removed though as generate_ssh_keys should only be invoked when public key file doesn't exist

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will update the test names.

I agree that the user should not call generate_ssh_keys even though they have a valid public key file. But I think it would still be good to handle and test this situation just in case this method is called by a future caller that fails to check this case. Again, I believe I was asked to handle all the different cases of public / private key being present or not present. ,

new_private_key = f.read()
self.assertEqual(self.private_key, new_private_key)

def test_public_key_file_exists_no_permissions(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

again, we don't need this test

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you would like me to remove this test, I can. Although @troydai asked me to test different cornercases that come to mind.

with self.assertRaises(CLIError):
generate_ssh_keys("", public_key_path)

def test_private_key_file_exists_no_permissions(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest test_error_raised_when_private_key_file_exists_no_permissions

public_key_path = private_key_path + ".pub"
generate_ssh_keys(private_key_path, public_key_path)

def test_private_key_file_exists_encrypted(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same: test_error_raised_when_private_key_file_exists_no_permissions

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will update, thanks

public_key_path = private_key_path + ".pub"
generate_ssh_keys(private_key_path, public_key_path)

def test_private_key_file_exists(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest: test_generate_public_key_from_existing_private_key_file

private_key = f.read()
self.assertEqual(self.private_key, private_key)

def test_private_key_file_new(self):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggest: `test_generate_new_private_key_files

# Check that public key returned is same as public key in public key path
with open(public_key_path, 'r') as f:
public_key = f.read()
self.assertEqual(public_key, new_public_key)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question: so you are sure that the keys generated at different time in the same machine would be the same?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this method asserts that the new_public_key returned by generate_ssh_keys is the same as the one that the method wrote to the public key file it created under public_key_path.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for all the feedback, btw.

@adewaleo adewaleo force-pushed the ova-GenSSHKeysVMUnsafe branch from 30209f3 to 4983dc5 Compare September 5, 2018 23:49
@adewaleo
Copy link
Contributor Author

adewaleo commented Sep 5, 2018

So I undid my last commit. Someone should let me know if it looks good so I can merge.

Copy link
Contributor

@williexu williexu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for the most part, just some small requested changes.

@@ -6,6 +6,8 @@ Release History
2.0.46
++++++
* Minor fixes.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace the Minor fixes entry

os.chmod(private_key_filepath, 0o600)
if os.path.isfile(private_key_filepath):
# try to use existing private key if it exists.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this empty line

logger.warning("Private SSH key file '%s' found in dir '%s'. "
"Generating new Public key file '%s'",
private_key_filepath, ssh_dir, public_key_filepath)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this empty line

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I just addressed these comments.

@williexu
Copy link
Contributor

williexu commented Sep 6, 2018

Merge when ci is green

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants