tests auth_log: Add new tests for NETLOGON
authorGary Lockyer <gary@catalyst.net.nz>
Sun, 9 Jul 2017 19:46:26 +0000 (07:46 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Mon, 24 Jul 2017 21:29:23 +0000 (23:29 +0200)
Tests for the logging of NETLOGON authentications in the
netr_ServerAuthenticate3 message processing

Test code based on the existing auth_log tests.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12865

Signed-off-by: Gary Lockyer <gary@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
Reviewed-by: Alexander Bokovoy <ab@samba.org>
python/samba/tests/auth_log_netlogon.py [new file with mode: 0644]
python/samba/tests/auth_log_netlogon_bad_creds.py [new file with mode: 0644]
selftest/knownfail.d/auth-logging [new file with mode: 0644]
source4/selftest/tests.py

diff --git a/python/samba/tests/auth_log_netlogon.py b/python/samba/tests/auth_log_netlogon.py
new file mode 100644 (file)
index 0000000..228fbe9
--- /dev/null
@@ -0,0 +1,131 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
+# 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 that exercise the auth logging for a successful netlogon attempt
+
+    NOTE: As the netlogon authentication is performed once per session,
+          there is only one test in this routine.  If another test is added
+          only the test executed first will generate the netlogon auth message
+"""
+
+import samba.tests
+import os
+from samba.samdb import SamDB
+import samba.tests.auth_log_base
+from samba.credentials import Credentials
+from samba.dcerpc import netlogon
+from samba.dcerpc.dcerpc import AS_SYSTEM_MAGIC_PATH_TOKEN
+from samba.auth import system_session
+from samba.tests import delete_force
+from samba.dsdb import UF_WORKSTATION_TRUST_ACCOUNT, UF_PASSWD_NOTREQD
+from samba.dcerpc.misc import SEC_CHAN_WKSTA
+
+
+class AuthLogTestsNetLogon(samba.tests.auth_log_base.AuthLogTestBase):
+
+    def setUp(self):
+        super(AuthLogTestsNetLogon, self).setUp()
+        self.lp      = samba.tests.env_loadparm()
+        self.creds   = Credentials()
+
+        self.session = system_session()
+        self.ldb = SamDB(
+            session_info=self.session,
+            credentials=self.creds,
+            lp=self.lp)
+
+        self.domain        = os.environ["DOMAIN"]
+        self.netbios_name  = "NetLogonGood"
+        self.machinepass   = "abcdefghij"
+        self.remoteAddress = AS_SYSTEM_MAGIC_PATH_TOKEN
+        self.base_dn       = self.ldb.domain_dn()
+        self.dn            = ("cn=%s,cn=users,%s" %
+                              (self.netbios_name, self.base_dn))
+
+        utf16pw = unicode(
+            '"' + self.machinepass.encode('utf-8') + '"', 'utf-8'
+        ).encode('utf-16-le')
+        self.ldb.add({
+            "dn": self.dn,
+            "objectclass": "computer",
+            "sAMAccountName": "%s$" % self.netbios_name,
+            "userAccountControl":
+                str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD),
+            "unicodePwd": utf16pw})
+
+    def tearDown(self):
+        super(AuthLogTestsNetLogon, self).tearDown()
+        delete_force(self.ldb, self.dn)
+
+    def _test_netlogon(self, binding, checkFunction):
+
+        def isLastExpectedMessage(msg):
+            return (
+                msg["type"] == "Authorization" and
+                msg["Authorization"]["serviceDescription"]  == "DCE/RPC" and
+                msg["Authorization"]["authType"]            == "schannel" and
+                msg["Authorization"]["transportProtection"] == "SEAL")
+
+        if binding:
+            binding = "[schannel,%s]" % binding
+        else:
+            binding = "[schannel]"
+
+        machine_creds = Credentials()
+        machine_creds.guess(self.get_loadparm())
+        machine_creds.set_secure_channel_type(SEC_CHAN_WKSTA)
+        machine_creds.set_password(self.machinepass)
+        machine_creds.set_username(self.netbios_name + "$")
+
+        netlogon_conn = netlogon.netlogon("ncalrpc:%s" % binding,
+                                          self.get_loadparm(),
+                                          machine_creds)
+
+        messages = self.waitForMessages(isLastExpectedMessage, netlogon_conn)
+        checkFunction(messages)
+
+    def netlogon_check(self, messages):
+
+        expected_messages = 5
+        self.assertEquals(expected_messages,
+                          len(messages),
+                          "Did not receive the expected number of messages")
+
+        # Check the first message it should be an Authorization
+        msg = messages[0]
+        self.assertEquals("Authorization", msg["type"])
+        self.assertEquals("DCE/RPC",
+                          msg["Authorization"]["serviceDescription"])
+        self.assertEquals("ncalrpc", msg["Authorization"]["authType"])
+        self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
+
+        # Check the fourth message it should be a NETLOGON Authentication
+        msg = messages[3]
+        self.assertEquals("Authentication", msg["type"])
+        self.assertEquals("NETLOGON",
+                          msg["Authentication"]["serviceDescription"])
+        self.assertEquals("ServerAuthenticate",
+                          msg["Authentication"]["authDescription"])
+        self.assertEquals("NT_STATUS_OK",
+                          msg["Authentication"]["status"])
+        self.assertEquals("HMAC-SHA256",
+                          msg["Authentication"]["passwordType"])
+
+    def test_netlogon(self):
+        self._test_netlogon("SEAL", self.netlogon_check)
diff --git a/python/samba/tests/auth_log_netlogon_bad_creds.py b/python/samba/tests/auth_log_netlogon_bad_creds.py
new file mode 100644 (file)
index 0000000..fedd8a1
--- /dev/null
@@ -0,0 +1,178 @@
+# Unix SMB/CIFS implementation.
+# Copyright (C) Andrew Bartlett <abartlet@samba.org> 2017
+# 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 that exercise auth logging for unsuccessful netlogon attempts.
+
+    NOTE: netlogon is only done once per session, so this file should only
+          test failed logons.  Adding a successful case will potentially break
+          the other tests, depending on the order of execution.
+"""
+
+import samba.tests
+import os
+from samba import NTSTATUSError
+from samba.samdb import SamDB
+import samba.tests.auth_log_base
+from samba.credentials import Credentials
+from samba.dcerpc import netlogon
+from samba.dcerpc.dcerpc import AS_SYSTEM_MAGIC_PATH_TOKEN
+from samba.auth import system_session
+from samba.tests import delete_force
+from samba.dsdb import UF_WORKSTATION_TRUST_ACCOUNT, UF_PASSWD_NOTREQD
+from samba.dcerpc.misc import SEC_CHAN_WKSTA
+
+
+class AuthLogTestsNetLogonBadCreds(samba.tests.auth_log_base.AuthLogTestBase):
+
+    def setUp(self):
+        super(AuthLogTestsNetLogonBadCreds, self).setUp()
+        self.lp      = samba.tests.env_loadparm()
+        self.creds   = Credentials()
+
+        self.session = system_session()
+        self.ldb = SamDB(
+            session_info=self.session,
+            credentials=self.creds,
+            lp=self.lp)
+
+        self.domain        = os.environ["DOMAIN"]
+        self.netbios_name  = "NetLogonBad"
+        self.machinepass   = "abcdefghij"
+        self.remoteAddress = AS_SYSTEM_MAGIC_PATH_TOKEN
+        self.base_dn       = self.ldb.domain_dn()
+        self.dn            = ("cn=%s,cn=users,%s" %
+                              (self.netbios_name, self.base_dn))
+
+        utf16pw = unicode(
+            '"' + self.machinepass.encode('utf-8') + '"', 'utf-8'
+        ).encode('utf-16-le')
+        self.ldb.add({
+            "dn": self.dn,
+            "objectclass": "computer",
+            "sAMAccountName": "%s$" % self.netbios_name,
+            "userAccountControl":
+                str(UF_WORKSTATION_TRUST_ACCOUNT | UF_PASSWD_NOTREQD),
+            "unicodePwd": utf16pw})
+
+    def tearDown(self):
+        super(AuthLogTestsNetLogonBadCreds, self).tearDown()
+        delete_force(self.ldb, self.dn)
+
+    def _test_netlogon(self, name, pwd, status, checkFunction):
+
+        def isLastExpectedMessage(msg):
+            return (
+                msg["type"] == "Authentication" and
+                msg["Authentication"]["serviceDescription"] == "NETLOGON" and
+                msg["Authentication"]["authDescription"] ==
+                "ServerAuthenticate" and
+                msg["Authentication"]["status"] == status)
+
+        machine_creds = Credentials()
+        machine_creds.guess(self.get_loadparm())
+        machine_creds.set_secure_channel_type(SEC_CHAN_WKSTA)
+        machine_creds.set_password(pwd)
+        machine_creds.set_username(name + "$")
+
+        try:
+            netlogon.netlogon("ncalrpc:[schannel]",
+                              self.get_loadparm(),
+                              machine_creds)
+            self.fail("NTSTATUSError not raised")
+        except NTSTATUSError:
+            pass
+
+        messages = self.waitForMessages(isLastExpectedMessage)
+        checkFunction(messages)
+
+    def netlogon_check(self, messages):
+
+        expected_messages = 4
+        self.assertEquals(expected_messages,
+                          len(messages),
+                          "Did not receive the expected number of messages")
+
+        # Check the first message it should be an Authorization
+        msg = messages[0]
+        self.assertEquals("Authorization", msg["type"])
+        self.assertEquals("DCE/RPC",
+                          msg["Authorization"]["serviceDescription"])
+        self.assertEquals("ncalrpc", msg["Authorization"]["authType"])
+        self.assertEquals("NONE", msg["Authorization"]["transportProtection"])
+
+    def test_netlogon_bad_machine_name(self):
+        self._test_netlogon("bad_name",
+                            self.machinepass,
+                            "NT_STATUS_NO_TRUST_SAM_ACCOUNT",
+                            self.netlogon_check)
+
+    def test_netlogon_bad_password(self):
+        self._test_netlogon(self.netbios_name,
+                            "badpass",
+                            "NT_STATUS_ACCESS_DENIED",
+                            self.netlogon_check)
+
+    def test_netlogon_password_DES(self):
+        """Logon failure that exercises the "DES" passwordType path.
+        """
+        def isLastExpectedMessage(msg):
+            return (
+                msg["type"] == "Authentication" and
+                msg["Authentication"]["serviceDescription"] == "NETLOGON" and
+                msg["Authentication"]["authDescription"] ==
+                "ServerAuthenticate" and
+                msg["Authentication"]["passwordType"] == "DES")
+
+        c = netlogon.netlogon("ncalrpc:[schannel]", self.get_loadparm())
+        creds = netlogon.netr_Credential()
+        c.netr_ServerReqChallenge(self.server, self.netbios_name, creds)
+        try:
+            c.netr_ServerAuthenticate3(self.server,
+                                       self.netbios_name,
+                                       SEC_CHAN_WKSTA,
+                                       self.netbios_name,
+                                       creds,
+                                       0)
+        except NTSTATUSError:
+            pass
+        self.waitForMessages(isLastExpectedMessage)
+
+    def test_netlogon_password_HMAC_MD5(self):
+        """Logon failure that exercises the "HMAC-MD5" passwordType path.
+        """
+        def isLastExpectedMessage(msg):
+            return (
+                msg["type"] == "Authentication" and
+                msg["Authentication"]["serviceDescription"] == "NETLOGON" and
+                msg["Authentication"]["authDescription"] ==
+                "ServerAuthenticate" and
+                msg["Authentication"]["passwordType"] == "HMAC-MD5")
+        c = netlogon.netlogon("ncalrpc:[schannel]", self.get_loadparm())
+        creds = netlogon.netr_Credential()
+        c.netr_ServerReqChallenge(self.server, self.netbios_name, creds)
+        try:
+            c.netr_ServerAuthenticate3(self.server,
+                                       self.netbios_name,
+                                       SEC_CHAN_WKSTA,
+                                       self.netbios_name,
+                                       creds,
+                                       0x00004000)
+        except NTSTATUSError:
+            pass
+        self.waitForMessages(isLastExpectedMessage)
diff --git a/selftest/knownfail.d/auth-logging b/selftest/knownfail.d/auth-logging
new file mode 100644 (file)
index 0000000..1f3532d
--- /dev/null
@@ -0,0 +1,8 @@
+# NETLOGON authentication logging tests, currently fail as the
+# code has not been implemented
+^samba.tests.auth_log_netlogon_bad_creds.samba.tests.auth_log_netlogon_bad_creds.AuthLogTestsNetLogonBadCreds.test_netlogon_bad_password\(ad_dc_ntvfs:local\)
+^samba.tests.auth_log_netlogon_bad_creds.samba.tests.auth_log_netlogon_bad_creds.AuthLogTestsNetLogonBadCreds.test_netlogon_bad_machine_name\(ad_dc_ntvfs:local\)
+^samba.tests.auth_log_netlogon_bad_creds.samba.tests.auth_log_netlogon_bad_creds.AuthLogTestsNetLogonBadCreds.test_netlogon_bad_password\(ad_dc:local\)
+^samba.tests.auth_log_netlogon_bad_creds.samba.tests.auth_log_netlogon_bad_creds.AuthLogTestsNetLogonBadCreds.test_netlogon_bad_machine_name\(ad_dc:local\)
+^samba.tests.auth_log_netlogon.samba.tests.auth_log_netlogon.AuthLogTestsNetLogon.test_netlogon\(ad_dc_ntvfs:local\)
+^samba.tests.auth_log_netlogon.samba.tests.auth_log_netlogon.AuthLogTestsNetLogon.test_netlogon\(ad_dc:local\)
index 44c0b08..c13af0c 100755 (executable)
@@ -636,6 +636,24 @@ if have_jansson_support and have_heimdal_support:
                            extra_args=['-U"$USERNAME%$PASSWORD"'],
                            environ={'CLIENT_IP': '127.0.0.11',
                                     'SOCKET_WRAPPER_DEFAULT_IFACE': 11})
+    planoldpythontestsuite("ad_dc:local", "samba.tests.auth_log_netlogon",
+                           extra_args=['-U"$USERNAME%$PASSWORD"'],
+                           environ={'CLIENT_IP': '127.0.0.11',
+                                    'SOCKET_WRAPPER_DEFAULT_IFACE': 11})
+    planoldpythontestsuite("ad_dc_ntvfs:local", "samba.tests.auth_log_netlogon",
+                           extra_args=['-U"$USERNAME%$PASSWORD"'],
+                           environ={'CLIENT_IP': '127.0.0.11',
+                                    'SOCKET_WRAPPER_DEFAULT_IFACE': 11})
+    planoldpythontestsuite("ad_dc:local",
+                           "samba.tests.auth_log_netlogon_bad_creds",
+                           extra_args=['-U"$USERNAME%$PASSWORD"'],
+                           environ={'CLIENT_IP': '127.0.0.11',
+                                    'SOCKET_WRAPPER_DEFAULT_IFACE': 11})
+    planoldpythontestsuite("ad_dc_ntvfs:local",
+                           "samba.tests.auth_log_netlogon_bad_creds",
+                           extra_args=['-U"$USERNAME%$PASSWORD"'],
+                           environ={'CLIENT_IP': '127.0.0.11',
+                                    'SOCKET_WRAPPER_DEFAULT_IFACE': 11})
 planoldpythontestsuite("ad_dc",
                        "samba.tests.net_join_no_spnego",
                        extra_args=['-U"$USERNAME%$PASSWORD"'])