Skip to content
This repository has been archived by the owner on Jul 4, 2024. It is now read-only.

Commit

Permalink
Hash password for IAAS/PAAS before sending then to the API.
Browse files Browse the repository at this point in the history
By hashing the pwd the API won't be able to check/validate
the password strength thus avoiding breaking the CLI when
the rules change as it's currently the case with the
gen-password option.
  • Loading branch information
MoiTux committed Sep 15, 2018
1 parent be0df58 commit 41646b1
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 14 deletions.
25 changes: 25 additions & 0 deletions gandi/cli/core/utils/password.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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, ))
5 changes: 3 additions & 2 deletions gandi/cli/modules/iaas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 3 additions & 2 deletions gandi/cli/modules/paas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))

Expand Down Expand Up @@ -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
Expand Down
18 changes: 12 additions & 6 deletions gandi/cli/tests/commands/test_paas.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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(),
Expand All @@ -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):
Expand Down Expand Up @@ -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(),
Expand All @@ -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)
Expand Down
28 changes: 24 additions & 4 deletions gandi/cli/tests/commands/test_vm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -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):
Expand Down Expand Up @@ -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(),
Expand All @@ -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(),
Expand All @@ -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):
Expand Down Expand Up @@ -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())
Expand All @@ -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):
Expand Down
24 changes: 24 additions & 0 deletions gandi/cli/tests/test_hash_password.py
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit 41646b1

Please sign in to comment.