From: Jo Sutton Date: Fri, 26 Apr 2024 01:20:54 +0000 (+1200) Subject: tests/krb5: Test performing NTLMSSP logons at different times X-Git-Tag: tdb-1.4.11~665 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ad0740751e8e47a0dcc6e537739ecadf95e90a70;p=thirdparty%2Fsamba.git tests/krb5: Test performing NTLMSSP logons at different times Signed-off-by: Jo Sutton Reviewed-by: Andrew Bartlett --- diff --git a/python/samba/tests/krb5/gmsa_tests.py b/python/samba/tests/krb5/gmsa_tests.py index bd3e06275e0..02014bf07da 100755 --- a/python/samba/tests/krb5/gmsa_tests.py +++ b/python/samba/tests/krb5/gmsa_tests.py @@ -100,6 +100,12 @@ class GmsaSeries: int(self.start_of_interval(n) + self.rollover_interval - two_minutes) ) + def outside_previous_password_valid_window(self, n: int) -> NtTime: + return NtTime(self.start_of_interval(n) + MAX_CLOCK_SKEW) + + def within_previous_password_valid_window(self, n: int) -> NtTime: + return NtTime(self.outside_previous_password_valid_window(n) - 1) + class GmsaTests(GkdiBaseTest, KDCBaseTest): def _as_req( @@ -1138,6 +1144,375 @@ class GmsaTests(GkdiBaseTest, KDCBaseTest): # Ensure that they match. self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + def test_gmsa_can_perform_gensec_ntlmssp_logon_when_current_key_is_valid(self): + """Test that we can perform a gensec logon at a time when we are sure + the current gMSA password is valid.""" + + password_interval = 18 + + 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, kerberos_enabled=False + ) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + + def test_gmsa_can_perform_gensec_ntlmssp_logon_when_current_key_is_expired(self): + """Test that we can perform a gensec logon using NTLMSSP at a time when + the current gMSA password has expired.""" + + password_interval = 40 + + 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, kerberos_enabled=False + ) + + # Set the time to the moment the original password has expired, and + # perform a gensec logon. + expired_time = series.start_of_interval(1) + self.set_db_time(samdb, expired_time) + + # Calculate the password with which to authenticate. + current_series = self.gmsa_series_for_account(samdb, creds, password_interval) + managed_pwd = self.expected_gmsa_password_blob( + samdb, + creds, + current_series.interval_gkid(0), + previous_gkid=current_series.interval_gkid(-1), + query_expiration_gkid=current_series.interval_gkid(1), + ) + + # Set the new password. + self.assertIsNotNone( + managed_pwd.passwords.current, "current password must be present" + ) + creds.set_utf16_password(managed_pwd.passwords.current) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + + def test_gmsa_can_perform_gensec_ntlmssp_logon_when_next_key_is_expired(self): + password_interval = 42 + + 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, kerberos_enabled=False + ) + + expired_time = series.start_of_interval(2) + self.set_db_time(samdb, expired_time) + + # Calculate the password with which to authenticate. + current_series = self.gmsa_series_for_account(samdb, creds, password_interval) + managed_pwd = self.expected_gmsa_password_blob( + samdb, + creds, + current_series.interval_gkid(0), + previous_gkid=current_series.interval_gkid(-1), + query_expiration_gkid=current_series.interval_gkid(1), + ) + + # Set the new password. + self.assertIsNotNone( + managed_pwd.passwords.current, "current password must be present" + ) + creds.set_utf16_password(managed_pwd.passwords.current) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + + def test_gmsa_can_perform_gensec_ntlmssp_logon_during_clock_skew_window_when_current_key_is_valid( + self, + ): + password_interval = 43 + + 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, kerberos_enabled=False + ) + + self.set_db_time(samdb, series.during_skew_window(0)) + + # Calculate the password with which to authenticate. + current_series = self.gmsa_series_for_account(samdb, creds, password_interval) + managed_pwd = self.expected_gmsa_password_blob( + samdb, + creds, + current_series.interval_gkid(0), + previous_gkid=current_series.interval_gkid(-1), + query_expiration_gkid=current_series.interval_gkid(1), + ) + + # Set the new password. + self.assertIsNotNone( + managed_pwd.passwords.current, "current password must be present" + ) + creds.set_utf16_password(managed_pwd.passwords.current) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + + def test_gmsa_can_perform_gensec_ntlmssp_logon_during_clock_skew_window_when_current_key_is_expired( + self, + ): + password_interval = 44 + + 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, kerberos_enabled=False + ) + + self.set_db_time(samdb, series.during_skew_window(1)) + + # Calculate the password with which to authenticate. + current_series = self.gmsa_series_for_account(samdb, creds, password_interval) + managed_pwd = self.expected_gmsa_password_blob( + samdb, + creds, + current_series.interval_gkid(0), + previous_gkid=current_series.interval_gkid(-1), + query_expiration_gkid=current_series.interval_gkid(1), + ) + + # Set the new password. + self.assertIsNotNone( + managed_pwd.passwords.current, "current password must be present" + ) + creds.set_utf16_password(managed_pwd.passwords.current) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + + def test_gmsa_can_perform_gensec_ntlmssp_logon_during_clock_skew_window_when_next_key_is_expired( + self, + ): + password_interval = 47 + + 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, kerberos_enabled=False + ) + + self.set_db_time(samdb, series.during_skew_window(2)) + + # Calculate the password with which to authenticate. + current_series = self.gmsa_series_for_account(samdb, creds, password_interval) + managed_pwd = self.expected_gmsa_password_blob( + samdb, + creds, + current_series.interval_gkid(0), + previous_gkid=current_series.interval_gkid(-1), + query_expiration_gkid=current_series.interval_gkid(1), + ) + + # Set the new password. + self.assertIsNotNone( + managed_pwd.passwords.current, "current password must be present" + ) + creds.set_utf16_password(managed_pwd.passwords.current) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + + def test_gmsa_can_perform_gensec_ntlmssp_logon_with_previous_password_within_five_minutes( + self, + ): + password_interval = 123 + + 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, kerberos_enabled=False + ) + + # Set the time to within five minutes of the original password’s expiry, + # and perform a gensec logon with the original password. + expired_time = series.within_previous_password_valid_window(1) + self.set_db_time(samdb, expired_time) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + + def test_gmsa_cannot_perform_gensec_ntlmssp_logon_with_previous_but_one_password_within_five_minutes( + self, + ): + password_interval = 123 + + 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, kerberos_enabled=False + ) + + # Set the time to within five minutes of the *following* password’s expiry, + # and perform a gensec logon with the original password. + expired_time = series.within_previous_password_valid_window(2) + self.set_db_time(samdb, expired_time) + + # Expect the gensec logon to fail. + self.gensec_ntlmssp_logon(creds, samdb, expect_success=False) + + def test_gmsa_can_perform_gensec_ntlmssp_logon_with_previous_password_beyond_five_minutes( + self, + ): + password_interval = 456 + + 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, kerberos_enabled=False + ) + + # Set the time to five minutes beyond the original password’s expiry, + # and try to perform a gensec logon with the original password. + expired_time = series.outside_previous_password_valid_window(1) + self.set_db_time(samdb, expired_time) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(creds.get_sid()), token_sids[0]) + + def test_gmsa_cannot_perform_gensec_ntlmssp_logon_with_previous_password_five_minutes_apart( + self, + ): + password_interval = 789 + + 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, kerberos_enabled=False + ) + gmsa_sid = creds.get_sid() + + # Set the time to after the original password’s expiry, and perform a + # gensec logon with the original password. + db_time = series.during_interval(1) + self.set_db_time(samdb, db_time) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(gmsa_sid), token_sids[0]) + + # Set the time to not quite five minutes later, and perform a gensec + # logon with the original password. + self.set_db_time(samdb, NtTime(db_time + MAX_CLOCK_SKEW - 1)) + + # Perform a gensec logon. + session = self.gensec_ntlmssp_logon(creds, samdb) + + # Ensure that the first SID contained within the security token is the gMSA’s SID. + token = session.security_token + token_sids = token.sids + self.assertGreater(len(token_sids), 0) + + # Ensure that they match. + self.assertEqual(security.dom_sid(gmsa_sid), token_sids[0]) + + # Now set the time to exactly five minutes later, and try to perform a + # gensec logon with the original password. + self.set_db_time(samdb, NtTime(db_time + MAX_CLOCK_SKEW)) + + # Expect the gensec logon to fail. + self.gensec_ntlmssp_logon(creds, samdb, expect_success=False) + def test_gmsa_can_perform_netlogon(self): creds = self.gmsa_account(kerberos_enabled=False) self._test_samlogon( diff --git a/selftest/knownfail.d/gmsa b/selftest/knownfail.d/gmsa new file mode 100644 index 00000000000..a046c6a6a2f --- /dev/null +++ b/selftest/knownfail.d/gmsa @@ -0,0 +1 @@ +^samba\.tests\.krb5\.gmsa_tests\.samba\.tests\.krb5\.gmsa_tests\.GmsaTests\.test_gmsa_cannot_perform_gensec_ntlmssp_logon_with_previous_password_five_minutes_apart\(ad_dc:local\)$