diff options
author | Tim Beale <timbeale@catalyst.net.nz> | 2018-06-21 15:04:00 +1200 |
---|---|---|
committer | Andrew Bartlett <abartlet@samba.org> | 2018-07-03 10:39:14 +0200 |
commit | 00d22122e5c0eb0c4e45c40b6a292628ab023f2d (patch) | |
tree | e6aac499551252309129ba62a50addff2b9b472c | |
parent | ccba77a9d8ead63acee11b15e1ca5f70afe168ad (diff) | |
download | samba-00d22122e5c0eb0c4e45c40b6a292628ab023f2d.tar.gz |
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 <timbeale@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Gary Lockyer <gary@catalyst.net.nz>
-rwxr-xr-x | source4/dsdb/tests/python/login_basics.py | 184 | ||||
-rwxr-xr-x | source4/selftest/tests.py | 28 | ||||
-rwxr-xr-x | testprogs/blackbox/join_ldapcmp.sh | 41 | ||||
-rwxr-xr-x | testprogs/blackbox/ldapcmp_restoredc.sh | 65 |
4 files changed, 314 insertions, 4 deletions
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] <host>") +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 <<EOF +Usage: $0 ORIG_DC_PREFIX RESTORED_DC_PREFIX +EOF +exit 1; +fi + +ORIG_DC_PREFIX_ABS="$1" +RESTORED_DC_PREFIX_ABS="$2" +shift 2 + +. `dirname $0`/subunit.sh + +basedn() { + SAMDB_PATH=$1 + $BINDIR/ldbsearch -H $SAMDB_PATH --basedn='' -s base defaultNamingContext | grep defaultNamingContext | awk '{print $2}' +} + +ldapcmp_with_orig() { + + DB1_PATH="tdb://$ORIG_DC_PREFIX_ABS/private/sam.ldb" + DB2_PATH="tdb://$RESTORED_DC_PREFIX_ABS/private/sam.ldb" + + # check if the 2 DCs are in different domains + DC1_BASEDN=$(basedn $DB1_PATH) + DC2_BASEDN=$(basedn $DB2_PATH) + BASE_DN_OPTS="" + + # if necessary, pass extra args to ldapcmp to handle the difference in base DNs + if [ "$DC1_BASEDN" != "$DC2_BASEDN" ] ; then + BASE_DN_OPTS="--base=$DC1_BASEDN --base2=$DC2_BASEDN" + fi + + # the restored DC will remove DNS entries for the old DC(s) + IGNORE_ATTRS="dnsRecord,dNSTombstoned" + + # DC2 joined DC1, so it will have different DRS info + IGNORE_ATTRS="$IGNORE_ATTRS,msDS-NC-Replica-Locations,msDS-HasInstantiatedNCs" + IGNORE_ATTRS="$IGNORE_ATTRS,interSiteTopologyGenerator" + + # there's a servicePrincipalName that uses the objectGUID of the DC's NTDS + # Settings that will differ between the two DCs + IGNORE_ATTRS="$IGNORE_ATTRS,servicePrincipalName" + + # the restore changes the new DC's password twice + IGNORE_ATTRS="$IGNORE_ATTRS,lastLogonTimestamp" + + # The RID pools get bumped during the restore process + IGNORE_ATTRS="$IGNORE_ATTRS,rIDAllocationPool,rIDAvailablePool" + + # these are just differences between provisioning a domain and joining a DC + IGNORE_ATTRS="$IGNORE_ATTRS,localPolicyFlags,operatingSystem,displayName" + + LDAPCMP_CMD="$PYTHON $BINDIR/samba-tool ldapcmp" + $LDAPCMP_CMD $DB1_PATH $DB2_PATH --two --filter=$IGNORE_ATTRS $BASE_DN_OPTS +} + +# check that the restored testenv DC basically matches the original +testit "orig_dc_matches" ldapcmp_with_orig + +exit $failed |