Skip to content

Commit addbfcc

Browse files
committed
vyos.utils: image-tools: T6353: Add password strength check and basic validation
1 parent c223859 commit addbfcc

File tree

4 files changed

+103
-1
lines changed

4 files changed

+103
-1
lines changed

debian/control

+2
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ Depends:
124124
# Live filesystem tools
125125
squashfs-tools,
126126
fuse-overlayfs,
127+
# Tools for checking password strength
128+
python3-cracklib,
127129
## End installer
128130
auditd,
129131
iputils-arping,

debian/vyos-1x.postinst

+13-1
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ if [ ! -x $PRECONFIG_SCRIPT ]; then
195195
EOF
196196
fi
197197

198+
# cracklib-runtime default database location
199+
CRACKLIB_DIR=/var/cache/cracklib
200+
CRACKLIB_DB=cracklib_dict
201+
198202
# create /opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script
199203
POSTCONFIG_SCRIPT=/opt/vyatta/etc/config/scripts/vyos-postconfig-bootup.script
200204
if [ ! -x $POSTCONFIG_SCRIPT ]; then
@@ -206,7 +210,15 @@ if [ ! -x $POSTCONFIG_SCRIPT ]; then
206210
# This script is executed at boot time after VyOS configuration is fully applied.
207211
# Any modifications required to work around unfixed bugs
208212
# or use services not available through the VyOS CLI system can be placed here.
209-
213+
#
214+
# T6353 - Just in case, check if cracklib was installed properly
215+
# If the database file is missing, re-install the runtime package
216+
#
217+
if [ ! -f "${CRACKLIB_DIR}/${CRACKLIB_DB}.pwd" ]; then
218+
mkdir -p $CRACKLIB_DIR
219+
/usr/sbin/create-cracklib-dict -o $CRACKLIB_DIR/$CRACKLIB_DB \
220+
/usr/share/dict/cracklib-small
221+
fi
210222
EOF
211223
fi
212224

python/vyos/utils/auth.py

+60
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,70 @@
1313
# You should have received a copy of the GNU Lesser General Public License along with this library;
1414
# if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
1515

16+
import cracklib
17+
import math
1618
import re
19+
import string
1720

21+
from enum import StrEnum
22+
from decimal import *
1823
from vyos.utils.process import cmd
1924

25+
DEFAULT_PASSWORD = 'vyos'
26+
LOW_ENTROPY_MSG = 'should be at least 8 characters long'
27+
28+
29+
class EPasswdStrength(StrEnum):
30+
WEAK = 'Weak'
31+
DECENT = 'Decent'
32+
STRONG = 'Strong'
33+
34+
35+
def calculate_entropy(charset: str, passwd: str) -> float:
36+
"""
37+
Calculate the entropy of a password based on the set of characters used
38+
Uses E = log2(R**L) formula, where
39+
- R is the range (length) of the character set
40+
- L is the length of password
41+
"""
42+
return math.log(math.pow(len(charset), len(passwd)), 2)
43+
44+
def check_passwd_strength(passwd: str) -> dict:
45+
""" Evaluates password strength and returns a check result dict """
46+
charset = (cracklib.ASCII_UPPERCASE + cracklib.ASCII_LOWERCASE +
47+
string.punctuation + string.digits)
48+
49+
result = {
50+
'strength': '',
51+
'errors': [],
52+
}
53+
54+
try:
55+
cracklib.FascistCheck(passwd)
56+
except ValueError as e:
57+
# The password is vulnerable to dictionary attack no matter the entropy
58+
if 'is' in str(e):
59+
msg = str(e).replace('is', 'should not be')
60+
else:
61+
msg = f'should not be {e}'
62+
result['strength'] = EPasswdStrength.WEAK
63+
result['errors'].append(msg)
64+
else:
65+
# Now check the password's entropy
66+
# Cast to Decimal for more precise rounding
67+
entropy = Decimal.from_float(calculate_entropy(charset, passwd))
68+
69+
match round(entropy):
70+
case e if e in range(0, 59):
71+
result['strength'] = EPasswdStrength.WEAK
72+
result['errors'].append(LOW_ENTROPY_MSG)
73+
case e if e in range(60, 119):
74+
result['strength'] = EPasswdStrength.DECENT
75+
case e if e >= 120:
76+
result['strength'] = EPasswdStrength.STRONG
77+
78+
return result
79+
2080
def make_password_hash(password):
2181
""" Makes a password hash for /etc/shadow using mkpasswd """
2282

src/op_mode/image_installer.py

+28
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
from vyos.remote import download
3737
from vyos.system import disk, grub, image, compat, raid, SYSTEM_CFG_VER
3838
from vyos.template import render
39+
from vyos.utils.auth import (
40+
check_passwd_strength,
41+
DEFAULT_PASSWORD,
42+
EPasswdStrength
43+
)
3944
from vyos.utils.io import ask_input, ask_yes_no, select_entry
4045
from vyos.utils.file import chmod_2775
4146
from vyos.utils.process import cmd, run, rc_cmd
@@ -83,6 +88,11 @@
8388
MSG_WARN_ROOT_SIZE_TOOSMALL: str = 'The size is too small. Try again'
8489
MSG_WARN_IMAGE_NAME_WRONG: str = 'The suggested name is unsupported!\n'\
8590
'It must be between 1 and 64 characters long and contains only the next characters: .+-_ a-z A-Z 0-9'
91+
92+
MSG_WARN_DEFAULT_PASSWORD: str = 'Default password used. Consider changing ' \
93+
'it on next login.'
94+
MSG_WARN_WEAK_PASSWORD: str = 'The password used is weak and can compromise ' \
95+
'the system security.\nFollowing issues \n@ERRORS@\n have been identified.'
8696
MSG_WARN_PASSWORD_CONFIRM: str = 'The entered values did not match. Try again'
8797
'Installing a different image flavor may cause functionality degradation or break your system.\n' \
8898
'Do you want to continue with installation?'
@@ -778,10 +788,28 @@ def install_image() -> None:
778788
while True:
779789
user_password: str = ask_input(MSG_INPUT_PASSWORD, no_echo=True,
780790
non_empty=True)
791+
792+
if user_password == DEFAULT_PASSWORD:
793+
print(MSG_WARN_DEFAULT_PASSWORD)
794+
else:
795+
result = check_passwd_strength(user_password)
796+
print('Password Strength: {}'.format(result['strength']))
797+
798+
passwd_weak = result['strength'] \
799+
not in [EPasswdStrength.DECENT, EPasswdStrength.STRONG]
800+
801+
if passwd_weak:
802+
err_list = [f' - {e}' for e in result['errors']]
803+
print(MSG_WARN_WEAK_PASSWORD.replace(
804+
'@ERRORS@', '\n'.join(err_list)
805+
))
806+
781807
confirm: str = ask_input(MSG_INPUT_PASSWORD_CONFIRM, no_echo=True,
782808
non_empty=True)
809+
783810
if user_password == confirm:
784811
break
812+
785813
print(MSG_WARN_PASSWORD_CONFIRM)
786814

787815
# ask for default console

0 commit comments

Comments
 (0)