1
0
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:
Jamie McClymont 2017-11-28 15:45:30 +13:00 committed by Andrew Bartlett
parent 704bbae25c
commit 698d28ee8c
10 changed files with 157 additions and 6 deletions

View File

@ -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,

View File

@ -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

View File

@ -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."""

View File

@ -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

View 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")

View 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)

View File

@ -0,0 +1 @@
^samba4.blackbox.provision-backend.openldap-mmr-backend

View File

@ -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")

View File

@ -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")

View File

@ -39,7 +39,7 @@ replace: minPwdAge
minPwdAge: -864000000000
-
replace: minPwdLength
minPwdLength: 7
minPwdLength: ${MIN_PWD_LENGTH}
-
replace: modifiedCount
modifiedCount: 1