From 00d22122e5c0eb0c4e45c40b6a292628ab023f2d Mon Sep 17 00:00:00 2001 From: Tim Beale Date: Thu, 21 Jun 2018 15:04:00 +1200 Subject: [PATCH] tests: Add a sub-set of tests to show the restored DC is sound + Add a new ldapcmp_restoredc.sh test that asserts that the original DC backed up (backupfromdc) matches the new restored DC. + Add a new join_ldapcmp.sh test that asserts we can join a given DC, and that the resulting DB matches the joined DC + Add a new login_basics.py test that sanity-checks Kerberos and NTLM user login works. (This reuses the password_lockout base code, without taking as long as the password_lockout tests do). Basic LDAP and SAMR connections are also tested as a side-effect. + run the netlogonsvc test against the restored DC to prove we can establish a netlogon connection. + run the same subset of rpc.echo tests that we do for RODC + run dbcheck over the new testenvs at the end of the test run Signed-off-by: Tim Beale Reviewed-by: Andrew Bartlett Reviewed-by: Gary Lockyer --- source4/dsdb/tests/python/login_basics.py | 184 ++++++++++++++++++++++ source4/selftest/tests.py | 28 +++- testprogs/blackbox/join_ldapcmp.sh | 41 +++++ testprogs/blackbox/ldapcmp_restoredc.sh | 65 ++++++++ 4 files changed, 314 insertions(+), 4 deletions(-) create mode 100755 source4/dsdb/tests/python/login_basics.py create mode 100755 testprogs/blackbox/join_ldapcmp.sh create mode 100755 testprogs/blackbox/ldapcmp_restoredc.sh diff --git a/source4/dsdb/tests/python/login_basics.py b/source4/dsdb/tests/python/login_basics.py new file mode 100755 index 00000000000..d1627881fa6 --- /dev/null +++ b/source4/dsdb/tests/python/login_basics.py @@ -0,0 +1,184 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Basic sanity-checks of user login. This sanity-checks that a user can login +# over both NTLM and Kerberos, that incorrect passwords are rejected, and that +# the user can change their password successfully. +# +# Copyright Andrew Bartlett 2018 +# +from __future__ import print_function +import optparse +import sys +from samba.tests.subunitrun import TestProgram, SubunitOptions +import samba.getopt as options +from samba.auth import system_session +from samba.credentials import MUST_USE_KERBEROS +from samba.dsdb import UF_NORMAL_ACCOUNT +from samba.samdb import SamDB +from password_lockout_base import BasePasswordTestCase + +sys.path.insert(0, "bin/python") + +parser = optparse.OptionParser("password_lockout.py [options] ") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option_group(options.VersionOptions(parser)) +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +subunitopts = SubunitOptions(parser) +parser.add_option_group(subunitopts) +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +host = args[0] + +lp = sambaopts.get_loadparm() +global_creds = credopts.get_credentials(lp) + + +# +# Tests start here +# +class BasicUserAuthTests(BasePasswordTestCase): + + def setUp(self): + self.host = host + self.host_url = host_url + self.lp = lp + self.global_creds = global_creds + self.ldb = SamDB(url=self.host_url, credentials=self.global_creds, + session_info=system_session(self.lp), lp=self.lp) + super(BasicUserAuthTests, self).setUp() + + def _test_login_basics(self, creds): + username = creds.get_username() + userpass = creds.get_password() + userdn = "cn=%s,cn=users,%s" % (username, self.base_dn) + if creds.get_kerberos_state() == MUST_USE_KERBEROS: + logoncount_relation = 'greater' + lastlogon_relation = 'greater' + print("Performs a lockout attempt against LDAP using Kerberos") + else: + logoncount_relation = 'equal' + lastlogon_relation = 'equal' + print("Performs a lockout attempt against LDAP using NTLM") + + # get the intial logon values for this user + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=("greater", 0), + logonCount=(logoncount_relation, 0), + lastLogon=("greater", 0), + lastLogonTimestamp=("greater", 0), + userAccountControl=UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg='Initial test setup...') + badPasswordTime = int(res[0]["badPasswordTime"][0]) + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + lastLogonTimestamp = int(res[0]["lastLogonTimestamp"][0]) + + test_creds = self.insta_creds(creds) + + # check logging in with the wrong password fails + test_creds.set_password("thatsAcomplPASS1xBAD") + self.assertLoginFailure(self.host_url, test_creds, self.lp) + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=("greater", badPasswordTime), + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl=UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg='Test login with wrong password') + badPasswordTime = int(res[0]["badPasswordTime"][0]) + + # check logging in with the correct password succeeds + test_creds.set_password(userpass) + user_ldb = SamDB(url=self.host_url, credentials=test_creds, lp=self.lp) + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lastLogon=('greater', lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl=UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg='Test login with correct password') + logonCount = int(res[0]["logonCount"][0]) + lastLogon = int(res[0]["lastLogon"][0]) + + # check that the user can change its password + new_password = "thatsAcomplPASS2" + user_ldb.modify_ldif(""" +dn: %s +changetype: modify +delete: userPassword +userPassword: %s +add: userPassword +userPassword: %s +""" % (userdn, userpass, new_password)) + + # discard the old creds (i.e. get rid of our valid Kerberos ticket) + del test_creds + test_creds = self.insta_creds(creds) + test_creds.set_password(userpass) + + # for Kerberos, logging in with the old password fails + if creds.get_kerberos_state() == MUST_USE_KERBEROS: + self.assertLoginFailure(self.host_url, test_creds, self.lp) + info_msg = 'Test Kerberos login with old password fails' + expectBadPwdTime = ("greater", badPasswordTime) + res = self._check_account(userdn, + badPwdCount=1, + badPasswordTime=expectBadPwdTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl=UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg=info_msg) + badPasswordTime = int(res[0]["badPasswordTime"][0]) + else: + # for NTLM, logging in with the old password succeeds + user_ldb = SamDB(url=self.host_url, credentials=test_creds, + lp=self.lp) + info_msg = 'Test NTLM login with old password succeeds' + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=logonCount, + lastLogon=lastLogon, + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl=UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg=info_msg) + + # check logging in with the new password succeeds + test_creds.set_password(new_password) + user_ldb = SamDB(url=self.host_url, credentials=test_creds, lp=self.lp) + res = self._check_account(userdn, + badPwdCount=0, + badPasswordTime=badPasswordTime, + logonCount=(logoncount_relation, logonCount), + lastLogon=(lastlogon_relation, lastLogon), + lastLogonTimestamp=lastLogonTimestamp, + userAccountControl=UF_NORMAL_ACCOUNT, + msDSUserAccountControlComputed=0, + msg='Test login with new password succeeds') + + def test_login_basics_krb5(self): + self._test_login_basics(self.lockout1krb5_creds) + + def test_login_basics_ntlm(self): + self._test_login_basics(self.lockout1ntlm_creds) + +host_url = "ldap://%s" % host + +TestProgram(module=__name__, opts=subunitopts) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index bca002f0d50..4504a2b09d8 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -811,6 +811,17 @@ plantestsuite_loadlist("samba4.ldap.sort.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [p plantestsuite_loadlist("samba4.ldap.vlv.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/vlv.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.ldap.linked_attributes.python(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [python, os.path.join(samba4srcdir, "dsdb/tests/python/linked_attributes.py"), '$PREFIX_ABS/ad_dc_ntvfs/private/sam.ldb', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) +# These should be the first tests run against testenvs created by backup/restore +for env in ['restoredc']: + # check that a restored DC matches the original DC (backupfromdc) + plantestsuite("samba4.blackbox.ldapcmp_restore", env, + ["PYTHON=%s" % python, + os.path.join(bbdir, "ldapcmp_restoredc.sh"), + '$PREFIX_ABS/backupfromdc', '$PREFIX_ABS/%s' % env]) + # basic test that we can join the testenv DC + plantestsuite("samba4.blackbox.join_ldapcmp", env, + ["PYTHON=%s" % python, os.path.join(bbdir, "join_ldapcmp.sh")]) + plantestsuite_loadlist("samba4.ldap.rodc.python(rodc)", "rodc", [python, os.path.join(samba4srcdir, "dsdb/tests/python/rodc.py"), @@ -857,6 +868,13 @@ for env in ["ad_dc_ntvfs"]: extra_path=[os.path.join(samba4srcdir, 'dsdb/tests/python')] ) +# this is a basic sanity-check of Kerberos/NTLM user login +for env in ["restoredc"]: + plantestsuite_loadlist("samba4.ldap.login_basics.python(%s)" % env, env, + [python, os.path.join(samba4srcdir, "dsdb/tests/python/login_basics.py"), + "$SERVER", '-U"$USERNAME%$PASSWORD"', "-W$DOMAIN", "--realm=$REALM", + '$LOADLIST', '$LISTOPT']) + planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.upgradeprovisionneeddc") planpythontestsuite("ad_dc:local", "samba.tests.posixacl", py3_compatible=True) planpythontestsuite("ad_dc_no_nss:local", "samba.tests.posixacl", py3_compatible=True) @@ -887,8 +905,8 @@ t = "rpc.samr.large-dc" plansmbtorture4testsuite(t, "vampire_dc", ['$SERVER', '-U$USERNAME%$PASSWORD', '--workgroup=$DOMAIN'], modname=("samba4.%s.one" % t)) plansmbtorture4testsuite(t, "vampire_dc", ['$SERVER', '-U$USERNAME%$PASSWORD', '--workgroup=$DOMAIN'], modname="samba4.%s.two" % t) -# some RODC testing -for env in ['rodc']: +# RPC smoke-tests for testenvs of interest (RODC, etc) +for env in ['rodc', 'restoredc']: plansmbtorture4testsuite('rpc.echo', env, ['ncacn_np:$SERVER', "-k", "yes", '-U$USERNAME%$PASSWORD', '--workgroup=$DOMAIN'], modname="samba4.rpc.echo") plansmbtorture4testsuite('rpc.echo', "%s:local" % env, ['ncacn_np:$SERVER', "-k", "yes", '-P', '--workgroup=$DOMAIN'], modname="samba4.rpc.echo") plansmbtorture4testsuite('rpc.echo', "%s:local" % env, ['ncacn_np:$SERVER', "-k", "no", '-Utestallowed\ account%$DC_PASSWORD', '--workgroup=$DOMAIN'], modname="samba4.rpc.echo.testallowed") @@ -1067,7 +1085,8 @@ for env in [ planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.kcc.kcc_utils") -for env in [ "simpleserver", "fileserver", "nt4_dc", "ad_dc", "ad_dc_ntvfs", "ad_member"]: +for env in [ "simpleserver", "fileserver", "nt4_dc", "ad_dc", "ad_dc_ntvfs", + "ad_member", "restoredc" ]: planoldpythontestsuite(env, "netlogonsvc", extra_path=[os.path.join(srcdir(), 'python/samba/tests')], name="samba.tests.netlogonsvc.python(%s)" % env) @@ -1090,7 +1109,8 @@ for env in ['vampire_dc', 'promoted_dc', 'rodc']: # TODO: Verifying the databases really should be a part of the # environment teardown. # check the databases are all OK. PLEASE LEAVE THIS AS THE LAST TEST -for env in ["ad_dc_ntvfs", "ad_dc", "fl2000dc", "fl2003dc", "fl2008r2dc", 'vampire_dc', 'promoted_dc']: +for env in ["ad_dc_ntvfs", "ad_dc", "fl2000dc", "fl2003dc", "fl2008r2dc", + 'vampire_dc', 'promoted_dc', 'backupfromdc', 'restoredc']: plantestsuite("samba4.blackbox.dbcheck(%s)" % env, env + ":local" , ["PYTHON=%s" % python, os.path.join(bbdir, "dbcheck.sh"), '$PREFIX/provision', configuration]) # cmocka tests not requiring a specific encironment diff --git a/testprogs/blackbox/join_ldapcmp.sh b/testprogs/blackbox/join_ldapcmp.sh new file mode 100755 index 00000000000..30d3e1e0192 --- /dev/null +++ b/testprogs/blackbox/join_ldapcmp.sh @@ -0,0 +1,41 @@ +#!/bin/sh +# Does a join against the testenv's DC and then runs ldapcmp on the resulting DB + +. `dirname $0`/subunit.sh + +TARGET_DIR="$PREFIX_ABS/join_$SERVER" + +cleanup_output_dir() +{ + if [ -d $TARGET_DIR ]; then + rm -fr $TARGET_DIR + fi +} + +SAMBA_TOOL="$PYTHON $BINDIR/samba-tool" + +join_dc() { + JOIN_ARGS="--targetdir=$TARGET_DIR --server=$SERVER -U$USERNAME%$PASSWORD" + $SAMBA_TOOL domain join $REALM dc $JOIN_ARGS --option="netbios name = TESTJOINDC" +} + +ldapcmp_result() { + DB1_PATH="tdb://$PREFIX_ABS/$SERVER/private/sam.ldb" + DB2_PATH="tdb://$TARGET_DIR/private/sam.ldb" + + # interSiteTopologyGenerator gets periodically updated. With the restored + # testenvs, it can sometimes point to the old/deleted DC object still + $SAMBA_TOOL ldapcmp $DB1_PATH $DB2_PATH --filter=interSiteTopologyGenerator +} + +cleanup_output_dir + +# check that we can join this DC +testit "check_dc_join" join_dc + +# check resulting DB matches server DC +testit "new_db_matches" ldapcmp_result + +cleanup_output_dir + +exit $failed diff --git a/testprogs/blackbox/ldapcmp_restoredc.sh b/testprogs/blackbox/ldapcmp_restoredc.sh new file mode 100755 index 00000000000..51951ba8ce2 --- /dev/null +++ b/testprogs/blackbox/ldapcmp_restoredc.sh @@ -0,0 +1,65 @@ +#!/bin/sh +# Does an ldapcmp between a newly restored testenv and the original testenv it +# was based on + +if [ $# -lt 2 ]; then +cat <