mirror of
https://github.com/samba-team/samba.git
synced 2025-02-22 05:57:43 +03:00
samba-tool: validate password early in domain provision
Checks password against default quality and length standards when it is entered, allowing a second chance to enter one (if interactive), rather than running through the provisioning process and bailing on an exception Includes unit tests for the newly-added python wrapper of check_password_quality plus black-box tests for the checks in samba-tool. Breaks an openldap test which uses an invalid password. BUG: https://bugzilla.samba.org/show_bug.cgi?id=9710 BUG: https://bugzilla.samba.org/show_bug.cgi?id=12235 Signed-off-by: Jamie McClymont <jamiemcclymont@catalyst.net.nz> Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>
This commit is contained in:
parent
704bbae25c
commit
698d28ee8c
@ -78,6 +78,17 @@ static PyObject *py_generate_random_machine_password(PyObject *self, PyObject *a
|
||||
return ret;
|
||||
}
|
||||
|
||||
static PyObject *py_check_password_quality(PyObject *self, PyObject *args)
|
||||
{
|
||||
char *pass;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "s", &pass)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return PyBool_FromLong(check_password_quality(pass));
|
||||
}
|
||||
|
||||
static PyObject *py_unix2nttime(PyObject *self, PyObject *args)
|
||||
{
|
||||
time_t t;
|
||||
@ -296,6 +307,12 @@ static PyMethodDef py_misc_methods[] = {
|
||||
"(based on random utf16 characters converted to utf8 or "
|
||||
"random ascii characters if 'unix charset' is not 'utf8')"
|
||||
"with a length >= min (at least 14) and <= max (at most 255)." },
|
||||
{ "check_password_quality", (PyCFunction)py_check_password_quality,
|
||||
METH_VARARGS, "check_password_quality(pass) -> bool\n"
|
||||
"Check password quality against Samba's check_password_quality,"
|
||||
"the implementation of Microsoft's rules:"
|
||||
"http://msdn.microsoft.com/en-us/subscriptions/cc786468%28v=ws.10%29.aspx"
|
||||
},
|
||||
{ "unix2nttime", (PyCFunction)py_unix2nttime, METH_VARARGS,
|
||||
"unix2nttime(timestamp) -> nttime" },
|
||||
{ "nttime2unix", (PyCFunction)py_nttime2unix, METH_VARARGS,
|
||||
|
@ -388,6 +388,7 @@ nttime2unix = _glue.nttime2unix
|
||||
unix2nttime = _glue.unix2nttime
|
||||
generate_random_password = _glue.generate_random_password
|
||||
generate_random_machine_password = _glue.generate_random_machine_password
|
||||
check_password_quality = _glue.check_password_quality
|
||||
strcasecmp_m = _glue.strcasecmp_m
|
||||
strstr_m = _glue.strstr_m
|
||||
is_ntvfs_fileserver_built = _glue.is_ntvfs_fileserver_built
|
||||
|
@ -84,7 +84,8 @@ from samba.dsdb import (
|
||||
|
||||
from samba.provision import (
|
||||
provision,
|
||||
ProvisioningError
|
||||
ProvisioningError,
|
||||
DEFAULT_MIN_PWD_LENGTH
|
||||
)
|
||||
|
||||
from samba.provision.common import (
|
||||
@ -370,8 +371,9 @@ class cmd_domain_provision(Command):
|
||||
|
||||
while True:
|
||||
adminpassplain = getpass("Administrator password: ")
|
||||
if not adminpassplain:
|
||||
self.errf.write("Invalid administrator password.\n")
|
||||
issue = self._adminpass_issue(adminpassplain)
|
||||
if issue:
|
||||
self.errf.write("%s.\n" % issue)
|
||||
else:
|
||||
adminpassverify = getpass("Retype password: ")
|
||||
if not adminpassplain == adminpassverify:
|
||||
@ -387,7 +389,11 @@ class cmd_domain_provision(Command):
|
||||
if domain is None:
|
||||
raise CommandError("No domain set!")
|
||||
|
||||
if not adminpass:
|
||||
if adminpass:
|
||||
issue = self._adminpass_issue(adminpass)
|
||||
if issue:
|
||||
raise CommandError(issue)
|
||||
else:
|
||||
self.logger.info("Administrator password will be set randomly!")
|
||||
|
||||
if function_level == "2000":
|
||||
@ -501,6 +507,20 @@ class cmd_domain_provision(Command):
|
||||
|
||||
self.logger.warning("No nameserver found in %s" % RESOLV_CONF)
|
||||
|
||||
def _adminpass_issue(self, adminpass):
|
||||
"""Returns error string for a bad administrator password,
|
||||
or None if acceptable"""
|
||||
|
||||
if len(adminpass.decode('utf-8')) < DEFAULT_MIN_PWD_LENGTH:
|
||||
return "Administrator password does not meet the default minimum" \
|
||||
" password length requirement (%d characters)" \
|
||||
% DEFAULT_MIN_PWD_LENGTH
|
||||
elif not samba.check_password_quality(adminpass):
|
||||
return "Administrator password does not meet the default" \
|
||||
" quality standards"
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class cmd_domain_dcpromo(Command):
|
||||
"""Promote an existing domain member or NT4 PDC to an AD DC."""
|
||||
|
@ -127,6 +127,8 @@ DEFAULT_DC_POLICY_GUID = "6AC1786C-016F-11D2-945F-00C04FB984F9"
|
||||
DEFAULTSITE = "Default-First-Site-Name"
|
||||
LAST_PROVISION_USN_ATTRIBUTE = "lastProvisionUSN"
|
||||
|
||||
DEFAULT_MIN_PWD_LENGTH = 7
|
||||
|
||||
|
||||
class ProvisionPaths(object):
|
||||
|
||||
@ -1309,7 +1311,8 @@ def fill_samdb(samdb, lp, names, logger, policyguid,
|
||||
"CONFIGDN": names.configdn,
|
||||
"POLICYGUID": policyguid,
|
||||
"DOMAIN_FUNCTIONALITY": str(domainFunctionality),
|
||||
"SAMBA_VERSION_STRING": version
|
||||
"SAMBA_VERSION_STRING": version,
|
||||
"MIN_PWD_LENGTH": str(DEFAULT_MIN_PWD_LENGTH)
|
||||
})
|
||||
|
||||
# If we are setting up a subdomain, then this has been replicated in, so we don't need to add it
|
||||
|
51
python/samba/tests/password_quality.py
Normal file
51
python/samba/tests/password_quality.py
Normal file
@ -0,0 +1,51 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Unix SMB/CIFS implementation.
|
||||
# Copyright (C) Catalyst IT Ltd. 2017
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
"""Tests for the python wrapper of the check_password_quality function
|
||||
"""
|
||||
|
||||
from samba import check_password_quality
|
||||
from samba.tests import TestCase, TestCaseInTempDir
|
||||
|
||||
class PasswordQualityTests(TestCase):
|
||||
def test_check_password_quality(self):
|
||||
self.assertFalse(check_password_quality(""),
|
||||
"empty password")
|
||||
self.assertFalse(check_password_quality("a"),
|
||||
"one char password")
|
||||
self.assertFalse(check_password_quality("aaaaaaaaaaaa"),
|
||||
"same char password")
|
||||
self.assertFalse(check_password_quality("BLA"),
|
||||
"multiple upcases password")
|
||||
self.assertFalse(check_password_quality("123"),
|
||||
"digits only")
|
||||
self.assertFalse(check_password_quality("matthiéu"),
|
||||
"not enough high symbols")
|
||||
self.assertFalse(check_password_quality("abcdééàçè"),
|
||||
"only lower case")
|
||||
self.assertFalse(check_password_quality("abcdééàçè+"),
|
||||
"only lower and symbols")
|
||||
self.assertTrue(check_password_quality("abcdééàçè+ढ"),
|
||||
"valid")
|
||||
self.assertTrue(check_password_quality("ç+ढ"),
|
||||
"valid")
|
||||
self.assertTrue(check_password_quality("A2e"),
|
||||
"valid")
|
||||
self.assertTrue(check_password_quality("BA2eLi443"),
|
||||
"valid")
|
56
python/samba/tests/samba_tool/provision_password_check.py
Normal file
56
python/samba/tests/samba_tool/provision_password_check.py
Normal file
@ -0,0 +1,56 @@
|
||||
# Unix SMB/CIFS implementation.
|
||||
# Copyright (C) Catalyst IT Ltd. 2017
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
from samba.tests.samba_tool.base import SambaToolCmdTest
|
||||
import os
|
||||
import shutil
|
||||
|
||||
class ProvisionPasswordTestCase(SambaToolCmdTest):
|
||||
"""Test for password validation in domain provision subcommand"""
|
||||
|
||||
def setUp(self):
|
||||
super(SambaToolCmdTest, self).setUp()
|
||||
self.tempsambadir = os.path.join(self.tempdir, "samba")
|
||||
os.mkdir(self.tempsambadir)
|
||||
|
||||
def _provision_with_password(self, password):
|
||||
return self.runsubcmd(
|
||||
"domain", "provision", "--realm=foo.example.com", "--domain=FOO",
|
||||
"--targetdir=%s" % self.tempsambadir, "--adminpass=%s" % password,
|
||||
"--use-ntvfs")
|
||||
|
||||
def test_short_and_low_quality(self):
|
||||
(result, out, err) = self._provision_with_password("foo")
|
||||
self.assertCmdFail(result)
|
||||
|
||||
def test_short(self):
|
||||
(result, out, err) = self._provision_with_password("Fo0!_9")
|
||||
self.assertCmdFail(result)
|
||||
self.assertRegexpMatches(err, r"minimum password length")
|
||||
|
||||
def test_low_quality(self):
|
||||
(result, out, err) = self._provision_with_password("aaaaaaaaaaaaaaaaa")
|
||||
self.assertCmdFail(result)
|
||||
self.assertRegexpMatches(err, r"quality standards")
|
||||
|
||||
def test_good(self):
|
||||
(result, out, err) = self._provision_with_password("Fo0!_9.")
|
||||
self.assertCmdSuccess(result, out, err)
|
||||
|
||||
def tearDown(self):
|
||||
super(SambaToolCmdTest, self).tearDown()
|
||||
shutil.rmtree(self.tempsambadir)
|
1
selftest/knownfail.d/validate-password-early
Normal file
1
selftest/knownfail.d/validate-password-early
Normal file
@ -0,0 +1 @@
|
||||
^samba4.blackbox.provision-backend.openldap-mmr-backend
|
@ -66,6 +66,7 @@ planpythontestsuite("none", "samba.tests.param", py3_compatible=True)
|
||||
planpythontestsuite("none", "samba.tests.upgrade")
|
||||
planpythontestsuite("none", "samba.tests.core", py3_compatible=True)
|
||||
planpythontestsuite("none", "samba.tests.provision")
|
||||
planpythontestsuite("none", "samba.tests.password_quality")
|
||||
planpythontestsuite("none", "samba.tests.samba3")
|
||||
planpythontestsuite("none", "samba.tests.strings")
|
||||
planpythontestsuite("none", "samba.tests.netcmd")
|
||||
|
@ -609,6 +609,7 @@ planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.user_virtualCryptSHA"
|
||||
planpythontestsuite("chgdcpass:local", "samba.tests.samba_tool.user_check_password_script")
|
||||
planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.group")
|
||||
planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.ntacl")
|
||||
planpythontestsuite("none", "samba.tests.samba_tool.provision_password_check")
|
||||
|
||||
planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.sites")
|
||||
planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.dnscmd")
|
||||
|
@ -39,7 +39,7 @@ replace: minPwdAge
|
||||
minPwdAge: -864000000000
|
||||
-
|
||||
replace: minPwdLength
|
||||
minPwdLength: 7
|
||||
minPwdLength: ${MIN_PWD_LENGTH}
|
||||
-
|
||||
replace: modifiedCount
|
||||
modifiedCount: 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user