Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VM/VMSS: incorporate credentials validation logic used by portal #2537

Merged
merged 4 commits into from Mar 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -523,15 +523,7 @@ def _validate_vm_create_auth(namespace):
StorageProfile.SASpecializedOSDisk]:
return

if len(namespace.admin_username) < 6 or namespace.admin_username.lower() == 'root':
# prompt for admin username if inadequate
from azure.cli.core.prompting import prompt, NoTTYException
try:
logger.warning("Cannot use admin username: %s. Admin username should be at "
"least 6 characters and cannot be 'root'", namespace.admin_username)
namespace.admin_username = prompt('Admin Username: ')
except NoTTYException:
raise CLIError('Please specify a valid admin username in non-interactive mode.')
_validate_admin_username(namespace.admin_username, namespace.os_type)

if not namespace.os_type:
raise CLIError("Unable to resolve OS type. Specify '--os-type' argument.")
Expand All @@ -551,13 +543,16 @@ def _validate_vm_create_auth(namespace):
"incorrect usage for authentication-type 'password': "
"[--admin-username USERNAME] --admin-password PASSWORD")

if not namespace.admin_password:
# prompt for admin password if not supplied
from azure.cli.core.prompting import prompt_pass, NoTTYException
try:
from azure.cli.core.prompting import prompt_pass, NoTTYException
try:
if not namespace.admin_password:
namespace.admin_password = prompt_pass('Admin Password: ', confirm=True)
except NoTTYException:
raise CLIError('Please specify both username and password in non-interactive mode.')
except NoTTYException:
raise CLIError('Please specify password in non-interactive mode.')

# validate password
_validate_admin_password(namespace.admin_password,
namespace.os_type)

elif namespace.authentication_type == 'ssh':

Expand All @@ -571,6 +566,48 @@ def _validate_vm_create_auth(namespace):
'/home/{}/.ssh/authorized_keys'.format(namespace.admin_username)


def _validate_admin_username(username, os_type):
if not username:
raise CLIError("admin user name can not be empty")
is_linux = (os_type.lower() == 'linux')
# pylint: disable=line-too-long
pattern = (r'[\\\/"\[\]:|<>+=;,?*@#()!A-Z]+' if is_linux else r'[\\\/"\[\]:|<>+=;,?*@]+')
linux_err = r'admin user name cannot contain upper case character A-Z, special characters \/"[]:|<>+=;,?*@#()! or start with $ or -'
win_err = r'admin user name cannot contain special characters \/"[]:|<>+=;,?*@# or ends with .'
if re.findall(pattern, username):
raise CLIError(linux_err if is_linux else win_err)
if is_linux and re.findall(r'^[$-]+', username):
raise CLIError(linux_err)
if not is_linux and username.endswith('.'):
raise CLIError(win_err)
disallowed_user_names = [
"administrator", "admin", "user", "user1", "test", "user2",
"test1", "user3", "admin1", "1", "123", "a", "actuser", "adm",
"admin2", "aspnet", "backup", "console", "david", "guest", "john",
"owner", "root", "server", "sql", "support", "support_388945a0",
"sys", "test2", "test3", "user4", "user5"]
if username.lower() in disallowed_user_names:
raise CLIError("This user name '{}' meets the general requirements, but is specifically disallowed for this image. Please try a different value.".format(username))


def _validate_admin_password(password, os_type):
is_linux = (os_type.lower() == 'linux')
max_length = 72 if is_linux else 123
min_length = 12
if len(password) not in range(min_length, max_length + 1):
raise CLIError('The pssword length must be between {} and {}'.format(min_length,
max_length))
contains_lower = re.findall('[a-z]+', password)
contains_upper = re.findall('[A-Z]+', password)
contains_digit = re.findall('[0-9]+', password)
contains_special_char = re.findall(r'[ `~!@#$%^&*()=+_\[\]{}\|;:.\/\'\",<>?]+', password)
count = len([x for x in [contains_lower, contains_upper,
contains_digit, contains_special_char] if x])
# pylint: disable=line-too-long
if count < 3:
raise CLIError('Password must have the 3 of the following: 1 lower case character, 1 upper case character, 1 number and 1 special character')


def validate_ssh_key(namespace):
string_or_file = (namespace.ssh_key_value or
os.path.join(os.path.expanduser('~'), '.ssh/id_rsa.pub'))
Expand Down
68 changes: 67 additions & 1 deletion src/command_modules/azure-cli-vm/tests/test_vm_actions.py
Expand Up @@ -12,7 +12,9 @@

from azure.cli.command_modules.vm._validators import (validate_ssh_key,
_is_valid_ssh_rsa_public_key,
_figure_out_storage_source)
_figure_out_storage_source,
_validate_admin_username,
_validate_admin_password)


class TestActions(unittest.TestCase):
Expand Down Expand Up @@ -65,3 +67,67 @@ def test_figure_out_storage_source(self):
self.assertFalse(src_disk)
self.assertFalse(src_snapshot)
self.assertEqual(src_blob_uri, test_data)

def test_validate_admin_username_linux(self):
Copy link
Member

Choose a reason for hiding this comment

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

👍 for unit tests!

# pylint: disable=line-too-long
err_invalid_char = r'admin user name cannot contain upper case character A-Z, special characters \/"[]:|<>+=;,?*@#()! or start with $ or -'

self._verify_username_with_ex('!@#', 'linux', err_invalid_char)
self._verify_username_with_ex('dav[', 'linux', err_invalid_char)
self._verify_username_with_ex('Adavid', 'linux', err_invalid_char)
self._verify_username_with_ex('-ddavid', 'linux', err_invalid_char)
self._verify_username_with_ex('', 'linux', 'admin user name can not be empty')
self._verify_username_with_ex('david', 'linux',
"This user name 'david' meets the general requirements, but is specifically disallowed for this image. Please try a different value.")

_validate_admin_username('d-avid1', 'linux')
_validate_admin_username('david1', 'linux')
_validate_admin_username('david1.', 'linux')

def test_validate_admin_username_windows(self):
# pylint: disable=line-too-long
err_invalid_char = r'admin user name cannot contain special characters \/"[]:|<>+=;,?*@# or ends with .'

self._verify_username_with_ex('!@#', 'windows', err_invalid_char)
self._verify_username_with_ex('dav[', 'windows', err_invalid_char)
self._verify_username_with_ex('dddivid.', 'windows', err_invalid_char)
self._verify_username_with_ex('john', 'windows',
"This user name 'john' meets the general requirements, but is specifically disallowed for this image. Please try a different value.")

_validate_admin_username('ADAVID', 'windows')
_validate_admin_username('d-avid1', 'windows')
_validate_admin_username('david1', 'windows')

def test_validate_admin_password_linux(self):
# pylint: disable=line-too-long
err_length = 'The pssword length must be between 12 and 72'
err_variety = 'Password must have the 3 of the following: 1 lower case character, 1 upper case character, 1 number and 1 special character'

self._verify_password_with_ex('te', 'linux', err_length)
self._verify_password_with_ex('P12' + '3' * 70, 'linux', err_length)
self._verify_password_with_ex('te12312312321', 'linux', err_variety)

_validate_admin_password('Password22345', 'linux')
_validate_admin_password('Password12!@#', 'linux')

def test_validate_admin_password_windows(self):
# pylint: disable=line-too-long
err_length = 'The pssword length must be between 12 and 123'
err_variety = 'Password must have the 3 of the following: 1 lower case character, 1 upper case character, 1 number and 1 special character'

self._verify_password_with_ex('P1', 'windows', err_length)
self._verify_password_with_ex('te14' + '3' * 120, 'windows', err_length)
self._verify_password_with_ex('te12345678997', 'windows', err_variety)

_validate_admin_password('Password22!!!', 'windows')
_validate_admin_password('Pas' + '1' * 70, 'windows')

def _verify_username_with_ex(self, admin_username, is_linux, expected_err):
with self.assertRaises(CLIError) as context:
_validate_admin_username(admin_username, is_linux)
self.assertTrue(expected_err in str(context.exception))

def _verify_password_with_ex(self, admin_password, is_linux, expected_err):
with self.assertRaises(CLIError) as context:
_validate_admin_password(admin_password, is_linux)
self.assertTrue(expected_err in str(context.exception))
12 changes: 6 additions & 6 deletions src/command_modules/azure-cli-vm/tests/test_vm_commands.py
Expand Up @@ -497,7 +497,7 @@ def test_vm_create_no_wait(self):
self.execute()

def body(self):
self.cmd('vm create -g {} -n {} --admin-username user12 --admin-password VerySecret! --authentication-type password --image UbuntuLTS --no-wait'.format(self.resource_group, self.name), checks=NoneCheck())
self.cmd('vm create -g {} -n {} --admin-username user12 --admin-password testPassword0 --authentication-type password --image UbuntuLTS --no-wait'.format(self.resource_group, self.name), checks=NoneCheck())
self.cmd('vm wait -g {} -n {} --custom "{}"'.format(self.resource_group, self.name, "instanceView.statuses[?code=='PowerState/running']"), checks=NoneCheck())
self.cmd('vm get-instance-view -g {} -n {}'.format(self.resource_group, self.name), checks=[
JMESPathCheck("length(instanceView.statuses[?code=='PowerState/running'])", 1)
Expand Down Expand Up @@ -547,7 +547,7 @@ def __init__(self, test_method):

def set_up(self):
super(VMExtensionScenarioTest, self).set_up()
self.cmd('vm create -n {} -g {} --image UbuntuLTS --authentication-type password --admin-username user11 --admin-password TestPass1@'.format(self.vm_name, self.resource_group))
self.cmd('vm create -n {} -g {} --image UbuntuLTS --authentication-type password --admin-username user11 --admin-password testPassword0'.format(self.vm_name, self.resource_group))

def test_vm_extension(self):
self.execute()
Expand Down Expand Up @@ -764,7 +764,7 @@ def test_vm_boot_diagnostics(self):
def set_up(self):
super(VMBootDiagnostics, self).set_up()
self.cmd('storage account create -g {} -n {} --sku Standard_LRS -l westus'.format(self.resource_group, self.storage_name))
self.cmd('vm create -n {} -g {} --image UbuntuLTS --authentication-type password --admin-username user11 --admin-password TestPass1@ --use-unmanaged-disk'.format(self.vm_name, self.resource_group))
self.cmd('vm create -n {} -g {} --image UbuntuLTS --authentication-type password --admin-username user11 --admin-password testPassword0 --use-unmanaged-disk'.format(self.vm_name, self.resource_group))

def body(self):
storage_uri = 'https://{}.blob.core.windows.net/'.format(self.storage_name)
Expand All @@ -789,7 +789,7 @@ def __init__(self, test_method):

def set_up(self):
super(VMSSExtensionInstallTest, self).set_up()
self.cmd('vmss create -n {} -g {} --image UbuntuLTS --authentication-type password --admin-username admin123 --admin-password TestPass1@'.format(self.vmss_name, self.resource_group))
self.cmd('vmss create -n {} -g {} --image UbuntuLTS --authentication-type password --admin-username admin123 --admin-password testPassword0'.format(self.vmss_name, self.resource_group))

def test_vmss_extension(self):
self.execute()
Expand Down Expand Up @@ -1369,7 +1369,7 @@ def body(self):
instance_count = 5
new_instance_count = 4

self.cmd('vmss create --admin-password Test1234@! --name {} -g {} --admin-username myadmin --image Win2012R2Datacenter --instance-count {}'
self.cmd('vmss create --admin-password testPassword0 --name {} -g {} --admin-username myadmin --image Win2012R2Datacenter --instance-count {}'
.format(vmss_name, self.resource_group, instance_count))

self.cmd('vmss show --name {} -g {}'.format(vmss_name, self.resource_group),
Expand Down Expand Up @@ -1441,7 +1441,7 @@ def body(self):
caching = 'ReadWrite'
upgrade_policy = 'automatic'

self.cmd('vmss create --image Debian --admin-password Test1234@! -l westus'
self.cmd('vmss create --image Debian --admin-password testPassword0 -l westus'
' -g {} -n {} --disable-overprovision --instance-count {}'
' --storage-caching {} --upgrade-policy-mode {}'
' --authentication-type password --admin-username myadmin --public-ip-address {}'
Expand Down
2 changes: 1 addition & 1 deletion src/command_modules/azure-cli-vm/tests/test_vm_defaults.py
Expand Up @@ -263,7 +263,7 @@ def test_linux_with_password(self):
ns.os_type = "LINux"
ns.authentication_type = 'password'
ns.admin_username = 'user12345'
ns.admin_password = 'verySecret!'
ns.admin_password = 'verySecret!!!'
_validate_vm_create_auth(ns)
# still has 'password'
self.assertEqual(ns.authentication_type, 'password')
Expand Down