diff --git a/gandi/cli/core/utils/password.py b/gandi/cli/core/utils/password.py index be028c00..1b00b69d 100644 --- a/gandi/cli/core/utils/password.py +++ b/gandi/cli/core/utils/password.py @@ -1,7 +1,9 @@ # coding: utf-8 """Contains methods to generate a random password.""" +import crypt import random +import re import string # remove backslash from generated password to avoid @@ -42,3 +44,26 @@ def mkpassword(length=16, chars=None, punctuation=None): random.shuffle(data) return ''.join(data) + + +def hash_password(password): + """ + Hash (if not already done) a string valid for use with PAAS/IAAS password + + WARNING: Using a hash password will make impossible for the API to + check/validate the password strength so you should check it + before. + + :param password: The string to hash + :type password: ``str`` + + :rtype: ``str`` + """ + + # crypt SHA-512 + if re.match('^\$6\$[a-zA-Z0-9\./]{16}\$[a-zA-Z0-9\./]{86}$', password): + return password + + salt = mkpassword(length=16, + chars=string.ascii_letters + string.digits + './') + return crypt.crypt(password, '$6$%s$' % (salt, )) diff --git a/gandi/cli/modules/iaas.py b/gandi/cli/modules/iaas.py index 48f0268e..9bfd49cb 100644 --- a/gandi/cli/modules/iaas.py +++ b/gandi/cli/modules/iaas.py @@ -8,6 +8,7 @@ from gandi.cli.core.base import GandiModule from gandi.cli.core.utils import randomstring +from gandi.cli.core.utils.password import hash_password from gandi.cli.modules.datacenter import Datacenter from gandi.cli.modules.sshkey import SshkeyHelper from gandi.cli.core.utils import MigrationNotFinalized @@ -180,7 +181,7 @@ def update(cls, id, memory, cores, console, password, background, vm_params['console'] = console if password: - vm_params['password'] = password + vm_params['password'] = hash_password(password) if max_memory: vm_params['vm_max_memory'] = max_memory @@ -224,7 +225,7 @@ def create(cls, datacenter, memory, cores, ip_version, bandwidth, vm_params['run'] = run if password: - vm_params['password'] = password + vm_params['password'] = hash_password(password) if ip_version: vm_params['ip_version'] = ip_version diff --git a/gandi/cli/modules/paas.py b/gandi/cli/modules/paas.py index 3aaf4095..028c6d08 100644 --- a/gandi/cli/modules/paas.py +++ b/gandi/cli/modules/paas.py @@ -4,6 +4,7 @@ import sys from gandi.cli.core.base import GandiModule +from gandi.cli.core.utils.password import hash_password from gandi.cli.modules.metric import Metric from gandi.cli.modules.vhost import Vhost from gandi.cli.modules.datacenter import Datacenter @@ -208,7 +209,7 @@ def update(cls, id, name, size, quantity, password, sshkey, upgrade, paas_params['quantity'] = quantity if password: - paas_params['password'] = password + paas_params['password'] = hash_password(password) paas_params.update(cls.convert_sshkey(sshkey)) @@ -251,7 +252,7 @@ def create(cls, name, size, type, quantity, duration, datacenter, vhosts, } if password: - paas_params['password'] = password + paas_params['password'] = hash_password(password) if quantity: paas_params['quantity'] = quantity diff --git a/gandi/cli/tests/commands/test_paas.py b/gandi/cli/tests/commands/test_paas.py index 21b8bbf6..624285a3 100644 --- a/gandi/cli/tests/commands/test_paas.py +++ b/gandi/cli/tests/commands/test_paas.py @@ -449,7 +449,9 @@ def test_delete_background(self): """) self.assertEqual(result.exit_code, 0) - def test_create_default(self): + @mock.patch('gandi.cli.modules.paas.hash_password') + def test_create_default(self, mock_hash_password): + mock_hash_password.return_value = '- hash pwd -' args = [] result = self.invoke_with_exceptions(paas.create, args, obj=GandiContextHelper(), @@ -468,10 +470,12 @@ def test_create_default(self): self.assertEqual(params['datacenter_id'], 3) self.assertEqual(params['size'], 's') self.assertEqual(params['duration'], '1m') - self.assertEqual(params['password'], 'ploki') + self.assertEqual(params['password'], '- hash pwd -') self.assertTrue(params['name'].startswith('paas')) - def test_create_size(self): + @mock.patch('gandi.cli.modules.paas.hash_password') + def test_create_size(self, mock_hash_password): + mock_hash_password.return_value = '- hash pwd -' args = ['--size', 's+'] result = self.invoke_with_exceptions(paas.create, args, obj=GandiContextHelper(), @@ -490,7 +494,7 @@ def test_create_size(self): self.assertEqual(params['datacenter_id'], 3) self.assertEqual(params['size'], 's+') self.assertEqual(params['duration'], '1m') - self.assertEqual(params['password'], 'ploki') + self.assertEqual(params['password'], '- hash pwd -') self.assertTrue(params['name'].startswith('paas')) def test_create_name(self): @@ -553,7 +557,9 @@ def test_create_name_vhost_ssl(self): self.assertEqual(result.exit_code, 0) - def test_create_datacenter_limited(self): + @mock.patch('gandi.cli.modules.paas.hash_password') + def test_create_datacenter_limited(self, mock_hash_password): + mock_hash_password.return_value = '- hash pwd -' args = ['--datacenter', 'FR-SD2'] result = self.invoke_with_exceptions(paas.create, args, obj=GandiContextHelper(), @@ -574,7 +580,7 @@ def test_create_datacenter_limited(self): self.assertEqual(params['datacenter_id'], 1) self.assertEqual(params['size'], 's') self.assertEqual(params['duration'], '1m') - self.assertEqual(params['password'], 'ploki') + self.assertEqual(params['password'], '- hash pwd -') self.assertTrue(params['name'].startswith('paas')) self.assertEqual(result.exit_code, 0) diff --git a/gandi/cli/tests/commands/test_vm.py b/gandi/cli/tests/commands/test_vm.py index e7b095c6..fb3517fa 100644 --- a/gandi/cli/tests/commands/test_vm.py +++ b/gandi/cli/tests/commands/test_vm.py @@ -834,7 +834,9 @@ def test_update_background(self): """) self.assertEqual(result.exit_code, 0) - def test_update_password(self): + @mock.patch('gandi.cli.modules.iaas.hash_password') + def test_update_password(self, mock_hash_password): + mock_hash_password.return_value = '- hash pwd -' args = ['server01', '--password'] result = self.invoke_with_exceptions(vm.update, args, input='plokiploki\nplokiploki\n') @@ -843,6 +845,9 @@ def test_update_password(self): password: \nRepeat for confirmation: \nUpdating your Virtual Machine server01. \rProgress: [###] 100.00% 00:00:00""") + params = self.api_calls['hosting.vm.update'][0][1] + self.assertEqual(params['password'], '- hash pwd -') + self.assertEqual(result.exit_code, 0) def test_update_console(self): @@ -942,7 +947,9 @@ def test_ssh_args(self): self.assertEqual(result.exit_code, 0) - def test_create_default_hostname_ok(self): + @mock.patch('gandi.cli.modules.iaas.hash_password') + def test_create_default_hostname_ok(self, mock_hash_password): + mock_hash_password.return_value = '- hash pwd -' args = ['--hostname', 'server500'] result = self.invoke_with_exceptions(vm.create, args, obj=GandiContextHelper(), @@ -956,9 +963,14 @@ def test_create_default_hostname_ok(self): \rProgress: [###] 100.00% 00:00:00 \n\ Your Virtual Machine server500 has been created.""") + params = self.api_calls['hosting.vm.create_from'][0][0] + self.assertEqual(params['password'], '- hash pwd -') + self.assertEqual(result.exit_code, 0) - def test_create_default_ok(self): + @mock.patch('gandi.cli.modules.iaas.hash_password') + def test_create_default_ok(self, mock_hash_password): + mock_hash_password.return_value = '- hash pwd -' args = [] result = self.invoke_with_exceptions(vm.create, args, obj=GandiContextHelper(), @@ -973,6 +985,9 @@ def test_create_default_ok(self): \rProgress: [###] 100.00% 00:00:00 \n\ Your Virtual Machine vm has been created.""") + params = self.api_calls['hosting.vm.create_from'][0][0] + self.assertEqual(params['password'], '- hash pwd -') + self.assertEqual(result.exit_code, 0) def test_create_ip_not_vlan_ko(self): @@ -1062,7 +1077,9 @@ def test_create_sshkey_ok(self): self.assertEqual(result.exit_code, 0) - def test_create_gen_password_root_ok(self): + @mock.patch('gandi.cli.modules.iaas.hash_password') + def test_create_gen_password_root_ok(self, mock_hash_password): + mock_hash_password.return_value = '- hash pwd -' args = ['--gen-password'] result = self.invoke_with_exceptions(vm.create, args, obj=GandiContextHelper()) @@ -1080,6 +1097,9 @@ def test_create_gen_password_root_ok(self): \rProgress: [###] 100.00% 00:00:00 \n\ Your Virtual Machine vm has been created.""") + params = self.api_calls['hosting.vm.create_from'][0][0] + self.assertEqual(params['password'], '- hash pwd -') + self.assertEqual(result.exit_code, 0) def test_create_gen_password_user_ok(self): diff --git a/gandi/cli/tests/test_hash_password.py b/gandi/cli/tests/test_hash_password.py new file mode 100644 index 00000000..7d2ff57f --- /dev/null +++ b/gandi/cli/tests/test_hash_password.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from ..core.utils.password import hash_password +from .compat import unittest, mock + + +class TestHashPwd(unittest.TestCase): + + @mock.patch('gandi.cli.core.utils.password.mkpassword') + def test_hash_pwd(self, mkpassword): + mkpassword.return_value = 'aSaltSting.12345' + + self.assertEqual(hash_password('.aPwd42!'), + '$6$aSaltSting.12345$' + 'kQ0e3QAP5MxJA4un4xkGCK4OwMc5dX/xKubYypmasAb' + 'U6ptnq5vyPi8IDfPm9zsKrUMKHhL056bD5rXsZqAt6.') + + def test_pwd_hashed(self): + pwd = ('$6$aSaltSting.12345$' + 'kQ0e3QAP5MxJA4un4xkGCK4OwMc5dX/xKubYypmasAb' + 'U6ptnq5vyPi8IDfPm9zsKrUMKHhL056bD5rXsZqAt6.') + + self.assertEqual(hash_password(pwd), pwd)