Skip to content

Commit

Permalink
Add support for getting the username from the certificate (Azure#2921)
Browse files Browse the repository at this point in the history
  • Loading branch information
N6UDP authored Jan 25, 2021
1 parent 67ff1fb commit b78f405
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 6 deletions.
5 changes: 5 additions & 0 deletions src/ssh/HISTORY.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Release History
===============

0.1.4
-----
* Change to use the first in the list of validprincipals as the default username
* Remove old paramiko dependency

0.1.3
-----
* Add support for using private IPs
Expand Down
8 changes: 6 additions & 2 deletions src/ssh/azext_ssh/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,14 @@ def _get_and_write_certificate(cmd, public_key_file, cert_file):
data = _prepare_jwk_data(public_key_file)
from azure.cli.core._profile import Profile
profile = Profile(cli_ctx=cmd.cli_ctx)
username, certificate = profile.get_msal_token(scopes, data)
# we used to use the username from the token but now we throw it away
_, certificate = profile.get_msal_token(scopes, data)
if not cert_file:
cert_file = public_key_file + "-aadcert.pub"
return _write_cert_file(certificate, cert_file), username.lower()
_write_cert_file(certificate, cert_file)
# instead we use the validprincipals from the cert due to mismatched upn and email in guest scenarios
username = ssh_utils.get_ssh_cert_principals(cert_file)[0]
return cert_file, username.lower()


def _prepare_jwk_data(public_key_file):
Expand Down
22 changes: 22 additions & 0 deletions src/ssh/azext_ssh/ssh_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ def create_ssh_keyfile(private_key_file):
subprocess.call(command, shell=platform.system() == 'Windows')


def get_ssh_cert_info(cert_file):
command = [_get_ssh_path("ssh-keygen"), "-L", "-f", cert_file]
logger.debug("Running ssh-keygen command %s", ' '.join(command))
return subprocess.check_output(command, shell=platform.system() == 'Windows').decode().splitlines()


def get_ssh_cert_principals(cert_file):
info = get_ssh_cert_info(cert_file)
principals = []
in_principal = False
for line in info:
if ":" in line:
in_principal = False
if "Principals:" in line:
in_principal = True
continue
if in_principal:
principals.append(line.strip())

return principals


def write_ssh_config(config_path, resource_group, vm_name, overwrite,
ip, username, cert_file, private_key_file):
file_utils.make_dirs_for_file(config_path)
Expand Down
6 changes: 4 additions & 2 deletions src/ssh/azext_ssh/tests/latest/test_custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ def do_op_side_effect(cmd, resource_group, vm_name, ssh_ip, public_key_file, pri
mock_do_op.assert_called_once_with(
cmd, "rg", "vm", "ip", "public", "private", False, mock.ANY)

@mock.patch('azext_ssh.ssh_utils.get_ssh_cert_principals')
@mock.patch('os.path.join')
@mock.patch('azext_ssh.custom._assert_args')
@mock.patch('azext_ssh.custom._check_or_create_public_private_files')
Expand All @@ -45,10 +46,11 @@ def do_op_side_effect(cmd, resource_group, vm_name, ssh_ip, public_key_file, pri
@mock.patch('azure.cli.core._profile.Profile.get_msal_token')
@mock.patch('azext_ssh.custom._write_cert_file')
def test_do_ssh_op(self, mock_write_cert, mock_ssh_creds, mock_get_mod_exp, mock_ip,
mock_check_files, mock_assert, mock_join):
mock_check_files, mock_assert, mock_join, mock_principal):
cmd = mock.Mock()
mock_op = mock.Mock()
mock_check_files.return_value = "public", "private"
mock_principal.return_value = ["username"]
mock_get_mod_exp.return_value = "modulus", "exponent"
mock_ssh_creds.return_value = "username", "certificate"
mock_join.return_value = "public-aadcert.pub"
Expand All @@ -61,7 +63,7 @@ def test_do_ssh_op(self, mock_write_cert, mock_ssh_creds, mock_get_mod_exp, mock
mock_get_mod_exp.assert_called_once_with("public")
mock_write_cert.assert_called_once_with("certificate", "public-aadcert.pub")
mock_op.assert_called_once_with(
"1.2.3.4", "username", mock_write_cert.return_value, "private")
"1.2.3.4", "username", "public-aadcert.pub", "private")

@mock.patch('azext_ssh.custom._assert_args')
@mock.patch('azext_ssh.custom._check_or_create_public_private_files')
Expand Down
3 changes: 1 addition & 2 deletions src/ssh/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from setuptools import setup, find_packages

VERSION = "0.1.3"
VERSION = "0.1.4"

CLASSIFIERS = [
'Development Status :: 4 - Beta',
Expand All @@ -22,7 +22,6 @@
]

DEPENDENCIES = [
'paramiko==2.6.0',
'cryptography==2.8.0'
]

Expand Down

0 comments on commit b78f405

Please sign in to comment.