]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
tests/krb5: Test that previous keys are counted as current keys following a gMSA...
authorJo Sutton <josutton@catalyst.net.nz>
Fri, 26 Apr 2024 01:43:57 +0000 (13:43 +1200)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 22 May 2024 20:33:36 +0000 (20:33 +0000)
Signed-off-by: Jo Sutton <josutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
python/samba/tests/krb5/gmsa_tests.py
python/samba/tests/krb5/raw_testcase.py
selftest/knownfail.d/gmsa

index 7077c0c95a5d16ec812c0356bb67a939095098eb..031e27bb8fecf5b58c859d419b8b92aaa528e08f 100755 (executable)
@@ -23,7 +23,7 @@ import os
 sys.path.insert(0, "bin/python")
 os.environ["PYTHONUNBUFFERED"] = "1"
 
-from typing import Callable, Iterable, NewType, Optional, Tuple, TypeVar
+from typing import Callable, Iterable, NewType, Optional, Set, Tuple, TypeVar
 
 import datetime
 from itertools import chain
@@ -41,6 +41,7 @@ from samba import (
 )
 from samba.dcerpc import gkdi, gmsa, misc, netlogon, security, srvsvc
 from samba.ndr import ndr_pack, ndr_unpack
+from samba.net import Net
 from samba.nt_time import (
     nt_time_delta_from_timedelta,
     nt_time_from_datetime,
@@ -58,6 +59,7 @@ from samba.gkdi import (
 )
 
 from samba.tests import connect_samdb
+from samba.tests.dckeytab import keytab_as_set
 from samba.tests.krb5 import kcrypto
 from samba.tests.gkdi import GkdiBaseTest, ROOT_KEY_START_TIME
 from samba.tests.krb5.kdc_base_test import KDCBaseTest
@@ -1554,6 +1556,97 @@ class GmsaTests(GkdiBaseTest, KDCBaseTest):
         # Expect the gensec logon to fail.
         self.gensec_ntlmssp_logon(creds, samdb, expect_success=False)
 
+    def test_gmsa_keys_when_previous_password_is_not_acceptable(self):
+        self._check_gmsa_keys(within_valid_window=False, expect_previous_keys=False)
+
+    def test_gmsa_keys_when_previous_password_is_acceptable(self):
+        self._check_gmsa_keys(within_valid_window=True, expect_previous_keys=True)
+
+    def _check_gmsa_keys(
+        self, *, within_valid_window: bool, expect_previous_keys: bool
+    ):
+        password_interval = 77
+
+        samdb = self.get_local_samdb()
+        series = self.gmsa_series(password_interval)
+        self.set_db_time(samdb, series.start_of_interval(0))
+
+        creds = self.gmsa_account(samdb=samdb, interval=password_interval)
+
+        if within_valid_window:
+            db_time = series.within_previous_password_valid_window(1)
+        else:
+            db_time = series.outside_previous_password_valid_window(1)
+        self.set_db_time(samdb, db_time)
+
+        gmsa_principal = f"{creds.get_username()}@{creds.get_realm()}"
+
+        ktfile = os.path.join(self.tempdir, "test.keytab")
+        self.addCleanup(self.rm_files, ktfile)
+
+        net = Net(None, self.get_lp())
+        net.export_keytab(
+            keytab=ktfile,
+            samdb=samdb,
+            principal=gmsa_principal,
+            only_current_keys=True,
+            as_for_AS_REQ=True,
+        )
+        self.assertTrue(os.path.exists(ktfile), "keytab was not created")
+
+        with open(ktfile, "rb") as bytes_kt:
+            keytab_bytes = bytes_kt.read()
+
+        keytab_set = keytab_as_set(keytab_bytes)
+        exported_etypes = {entry[1] for entry in keytab_set}
+
+        # Ensure that the AES keys were exported.
+        self.assertLessEqual(
+            {kcrypto.Enctype.AES256, kcrypto.Enctype.AES128}, exported_etypes
+        )
+
+        def fill_keytab(
+            creds: KerberosCredentials,
+            keytab: Set[Tuple[str, kcrypto.Enctype, int, bytes]],
+            etypes: Iterable[kcrypto.Enctype],
+        ) -> None:
+            for etype in etypes:
+                key = self.TicketDecryptionKey_from_creds(creds, etype=etype)
+                kvno = 2
+                entry = gmsa_principal, etype, kvno, key.key.contents
+
+                self.assertNotIn(entry, keytab, "key already present in keytab")
+                keytab.add(entry)
+
+        expected_keytab = set()
+
+        if expect_previous_keys:
+            # Fill the expected keytab with the previous keys.
+            fill_keytab(creds, expected_keytab, exported_etypes)
+
+        # Calculate the new password.
+        managed_pwd = self.expected_gmsa_password_blob(
+            samdb,
+            creds,
+            series.interval_gkid(1),
+            previous_gkid=series.interval_gkid(0),
+            query_expiration_gkid=series.interval_gkid(2),
+        )
+
+        # Set the new password.
+        self.assertIsNotNone(
+            managed_pwd.passwords.current, "current password must be present"
+        )
+        creds.set_utf16_password(managed_pwd.passwords.current)
+
+        # Clear the initial set of keys associated with this credentials object.
+        creds.clear_forced_keys()
+        # Add the current keys to the expected keytab.
+        fill_keytab(creds, expected_keytab, exported_etypes)
+
+        # Ensure the keytab is as we expect.
+        self.assertEqual(expected_keytab, keytab_set)
+
     def test_gmsa_can_perform_netlogon(self):
         self._test_samlogon(
             self.gmsa_account(kerberos_enabled=False),
index 96fbfaa5f9e64c088a4ff49ee69d6a9dd6ecb146..ef9498cd251af4792d48d7385a89d8ff956e7b28 100644 (file)
@@ -532,6 +532,9 @@ class KerberosCredentials(Credentials):
         etype = int(etype)
         return self.forced_keys.get(etype)
 
+    def clear_forced_keys(self):
+        self.forced_keys.clear()
+
     def set_forced_salt(self, salt):
         self.forced_salt = bytes(salt)
 
index d0a058e6bace5bf461500803e32b69d6506df54f..e8701a5db0c3730c2ade146b04d82669ca213309 100644 (file)
@@ -1,3 +1,4 @@
+^samba\.tests\.krb5\.gmsa_tests\.samba\.tests\.krb5\.gmsa_tests\.GmsaTests\.test_gmsa_keys_when_previous_password_is_acceptable\(ad_dc:local\)$
 # The unencrypted simple bind fails because the ad_dc environment sets ‘ldap
 # server require strong auth = yes’.
 ^samba\.tests\.krb5\.gmsa_tests\.samba\.tests\.krb5\.gmsa_tests\.GmsaTests\.test_retrieving_password_after_unencrypted_simple_bind\(ad_dc:local\)$