dsdb: audit samdb and password changes
[nivanova/samba-autobuild/.git] / python / samba / tests / audit_log_dsdb.py
1 # Tests for SamDb password change audit logging.
2 # Copyright (C) Andrew Bartlett <abartlet@samba.org> 2018
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 #
17
18 from __future__ import print_function
19 """Tests for the SamDb logging of password changes.
20 """
21
22 import samba.tests
23 from samba.dcerpc.messaging import MSG_DSDB_LOG, DSDB_EVENT_NAME
24 from samba.samdb import SamDB
25 from samba.auth import system_session
26 import os
27 import time
28 from samba.tests.audit_log_base import AuditLogTestBase
29 from samba.tests import delete_force
30 from samba.net import Net
31 import samba
32 from samba.dcerpc import security, lsa
33
34 USER_NAME = "auditlogtestuser"
35 USER_PASS = samba.generate_random_password(32, 32)
36 SECOND_USER_NAME = "auditlogtestuser02"
37 SECOND_USER_PASS = samba.generate_random_password(32, 32)
38
39
40 class AuditLogDsdbTests(AuditLogTestBase):
41
42     def setUp(self):
43         self.message_type = MSG_DSDB_LOG
44         self.event_type   = DSDB_EVENT_NAME
45         super(AuditLogDsdbTests, self).setUp()
46
47         self.remoteAddress = os.environ["CLIENT_IP"]
48         self.server_ip = os.environ["SERVER_IP"]
49
50         host = "ldap://%s" % os.environ["SERVER"]
51         self.ldb = SamDB(url=host,
52                          session_info=system_session(),
53                          credentials=self.get_credentials(),
54                          lp=self.get_loadparm())
55         self.server = os.environ["SERVER"]
56
57         # Gets back the basedn
58         self.base_dn = self.ldb.domain_dn()
59
60         # Get the old "dSHeuristics" if it was set
61         dsheuristics = self.ldb.get_dsheuristics()
62
63         # Set the "dSHeuristics" to activate the correct "userPassword"
64         # behaviour
65         self.ldb.set_dsheuristics("000000001")
66
67         # Reset the "dSHeuristics" as they were before
68         self.addCleanup(self.ldb.set_dsheuristics, dsheuristics)
69
70         # Get the old "minPwdAge"
71         minPwdAge = self.ldb.get_minPwdAge()
72
73         # Set it temporarily to "0"
74         self.ldb.set_minPwdAge("0")
75         self.base_dn = self.ldb.domain_dn()
76
77         # Reset the "minPwdAge" as it was before
78         self.addCleanup(self.ldb.set_minPwdAge, minPwdAge)
79
80         # (Re)adds the test user USER_NAME with password USER_PASS
81         delete_force(self.ldb, "cn=" + USER_NAME + ",cn=users," + self.base_dn)
82         delete_force(
83             self.ldb,
84             "cn=" + SECOND_USER_NAME + ",cn=users," + self.base_dn)
85         self.ldb.add({
86             "dn": "cn=" + USER_NAME + ",cn=users," + self.base_dn,
87             "objectclass": "user",
88             "sAMAccountName": USER_NAME,
89             "userPassword": USER_PASS
90         })
91
92     #
93     # Discard the messages from the setup code
94     #
95     def discardSetupMessages(self, dn):
96         messages = self.waitForMessages(2, dn=dn)
97         self.discardMessages()
98
99
100     def tearDown(self):
101         self.discardMessages()
102         super(AuditLogDsdbTests, self).tearDown()
103
104     def waitForTransaction(self, connection=None):
105         """Wait for a transaction message to arrive
106         The connection is passed through to keep the connection alive
107         until all the logging messages have been received.
108         """
109
110         self.connection = connection
111
112         start_time = time.time()
113         while self.context["txnMessage"] == "":
114             self.msg_ctx.loop_once(0.1)
115             if time.time() - start_time > 1:
116                 self.connection = None
117                 return ""
118
119         self.connection = None
120         return self.context["txnMessage"]
121
122     def test_net_change_password(self):
123
124         dn = "CN=" + USER_NAME + ",CN=Users," + self.base_dn
125         self.discardSetupMessages(dn)
126
127         creds = self.insta_creds(template=self.get_credentials())
128
129         lp = self.get_loadparm()
130         net = Net(creds, lp, server=self.server)
131         password = "newPassword!!42"
132
133         net.change_password(newpassword=password.encode('utf-8'),
134                             username=USER_NAME,
135                             oldpassword=USER_PASS)
136
137         messages = self.waitForMessages(1, net, dn=dn)
138         print("Received %d messages" % len(messages))
139         self.assertEquals(1,
140                           len(messages),
141                           "Did not receive the expected number of messages")
142
143         audit = messages[0]["dsdbChange"]
144         self.assertEquals("Modify", audit["operation"])
145         self.assertFalse(audit["performedAsSystem"])
146         self.assertTrue(dn.lower(), audit["dn"].lower())
147         self.assertRegexpMatches(audit["remoteAddress"],
148                                  self.remoteAddress)
149         session_id = self.get_session()
150         self.assertEquals(session_id, audit["sessionId"])
151         service_description = self.get_service_description()
152         self.assertEquals(service_description, "DCE/RPC")
153         self.assertTrue(self.is_guid(audit["transactionId"]))
154
155         attributes = audit["attributes"]
156         self.assertEquals(1, len(attributes))
157         actions = attributes["clearTextPassword"]["actions"]
158         self.assertEquals(1, len(actions))
159         self.assertTrue(actions[0]["redacted"])
160         self.assertEquals("replace", actions[0]["action"])
161
162     def test_net_set_password(self):
163
164         dn = "CN=" + USER_NAME + ",CN=Users," + self.base_dn
165         self.discardSetupMessages(dn)
166
167         creds = self.insta_creds(template=self.get_credentials())
168
169         lp = self.get_loadparm()
170         net = Net(creds, lp, server=self.server)
171         password = "newPassword!!42"
172         domain = lp.get("workgroup")
173
174         net.set_password(newpassword=password.encode('utf-8'),
175                          account_name=USER_NAME,
176                          domain_name=domain)
177         messages = self.waitForMessages(1, net, dn=dn)
178         print("Received %d messages" % len(messages))
179         self.assertEquals(1,
180                           len(messages),
181                           "Did not receive the expected number of messages")
182         audit = messages[0]["dsdbChange"]
183         self.assertEquals("Modify", audit["operation"])
184         self.assertFalse(audit["performedAsSystem"])
185         self.assertEquals(dn, audit["dn"])
186         self.assertRegexpMatches(audit["remoteAddress"],
187                                  self.remoteAddress)
188         session_id = self.get_session()
189         self.assertEquals(session_id, audit["sessionId"])
190         service_description = self.get_service_description()
191         self.assertEquals(service_description, "DCE/RPC")
192         self.assertTrue(self.is_guid(audit["transactionId"]))
193
194         attributes = audit["attributes"]
195         self.assertEquals(1, len(attributes))
196         actions = attributes["clearTextPassword"]["actions"]
197         self.assertEquals(1, len(actions))
198         self.assertTrue(actions[0]["redacted"])
199         self.assertEquals("replace", actions[0]["action"])
200
201     def test_ldap_change_password(self):
202
203         dn = "cn=" + USER_NAME + ",cn=users," + self.base_dn
204         self.discardSetupMessages(dn)
205
206         new_password = samba.generate_random_password(32, 32)
207         dn = "cn=" + USER_NAME + ",cn=users," + self.base_dn
208         self.ldb.modify_ldif(
209             "dn: " + dn + "\n" +
210             "changetype: modify\n" +
211             "delete: userPassword\n" +
212             "userPassword: " + USER_PASS + "\n" +
213             "add: userPassword\n" +
214             "userPassword: " + new_password + "\n")
215
216         messages = self.waitForMessages(1)
217         print("Received %d messages" % len(messages))
218         self.assertEquals(1,
219                           len(messages),
220                           "Did not receive the expected number of messages")
221
222         audit = messages[0]["dsdbChange"]
223         self.assertEquals("Modify", audit["operation"])
224         self.assertFalse(audit["performedAsSystem"])
225         self.assertEquals(dn, audit["dn"])
226         self.assertRegexpMatches(audit["remoteAddress"],
227                                  self.remoteAddress)
228         self.assertTrue(self.is_guid(audit["sessionId"]))
229         session_id = self.get_session()
230         self.assertEquals(session_id, audit["sessionId"])
231         service_description = self.get_service_description()
232         self.assertEquals(service_description, "LDAP")
233
234         attributes = audit["attributes"]
235         self.assertEquals(1, len(attributes))
236         actions = attributes["userPassword"]["actions"]
237         self.assertEquals(2, len(actions))
238         self.assertTrue(actions[0]["redacted"])
239         self.assertEquals("delete", actions[0]["action"])
240         self.assertTrue(actions[1]["redacted"])
241         self.assertEquals("add", actions[1]["action"])
242
243     def test_ldap_replace_password(self):
244
245         dn = "cn=" + USER_NAME + ",cn=users," + self.base_dn
246         self.discardSetupMessages(dn)
247
248         new_password = samba.generate_random_password(32, 32)
249         self.ldb.modify_ldif(
250             "dn: " + dn + "\n" +
251             "changetype: modify\n" +
252             "replace: userPassword\n" +
253             "userPassword: " + new_password + "\n")
254
255         messages = self.waitForMessages(1, dn=dn)
256         print("Received %d messages" % len(messages))
257         self.assertEquals(1,
258                           len(messages),
259                           "Did not receive the expected number of messages")
260
261         audit = messages[0]["dsdbChange"]
262         self.assertEquals("Modify", audit["operation"])
263         self.assertFalse(audit["performedAsSystem"])
264         self.assertTrue(dn.lower(), audit["dn"].lower())
265         self.assertRegexpMatches(audit["remoteAddress"],
266                                  self.remoteAddress)
267         self.assertTrue(self.is_guid(audit["sessionId"]))
268         session_id = self.get_session()
269         self.assertEquals(session_id, audit["sessionId"])
270         service_description = self.get_service_description()
271         self.assertEquals(service_description, "LDAP")
272         self.assertTrue(self.is_guid(audit["transactionId"]))
273
274         attributes = audit["attributes"]
275         self.assertEquals(1, len(attributes))
276         actions = attributes["userPassword"]["actions"]
277         self.assertEquals(1, len(actions))
278         self.assertTrue(actions[0]["redacted"])
279         self.assertEquals("replace", actions[0]["action"])
280
281     def test_ldap_add_user(self):
282
283         # The setup code adds a user, so we check for the dsdb events
284         # generated by it.
285         dn = "cn=" + USER_NAME + ",cn=users," + self.base_dn
286         messages = self.waitForMessages(2, dn=dn)
287         print("Received %d messages" % len(messages))
288         self.assertEquals(2,
289                           len(messages),
290                           "Did not receive the expected number of messages")
291
292         audit = messages[1]["dsdbChange"]
293         self.assertEquals("Add", audit["operation"])
294         self.assertFalse(audit["performedAsSystem"])
295         self.assertEquals(dn, audit["dn"])
296         self.assertRegexpMatches(audit["remoteAddress"],
297                                  self.remoteAddress)
298         session_id = self.get_session()
299         self.assertEquals(session_id, audit["sessionId"])
300         service_description = self.get_service_description()
301         self.assertEquals(service_description, "LDAP")
302         self.assertTrue(self.is_guid(audit["sessionId"]))
303         self.assertTrue(self.is_guid(audit["transactionId"]))
304
305         attributes = audit["attributes"]
306         self.assertEquals(3, len(attributes))
307
308         actions = attributes["objectclass"]["actions"]
309         self.assertEquals(1, len(actions))
310         self.assertEquals("add", actions[0]["action"])
311         self.assertEquals(1, len(actions[0]["values"]))
312         self.assertEquals("user", actions[0]["values"][0]["value"])
313
314         actions = attributes["sAMAccountName"]["actions"]
315         self.assertEquals(1, len(actions))
316         self.assertEquals("add", actions[0]["action"])
317         self.assertEquals(1, len(actions[0]["values"]))
318         self.assertEquals(USER_NAME, actions[0]["values"][0]["value"])
319
320         actions = attributes["userPassword"]["actions"]
321         self.assertEquals(1, len(actions))
322         self.assertEquals("add", actions[0]["action"])
323         self.assertTrue(actions[0]["redacted"])
324
325     def test_samdb_delete_user(self):
326
327         dn = "cn=" + USER_NAME + ",cn=users," + self.base_dn
328         self.discardSetupMessages(dn)
329
330         self.ldb.deleteuser(USER_NAME)
331
332         messages = self.waitForMessages(2, dn=dn)
333         print("Received %d messages" % len(messages))
334         self.assertEquals(2,
335                           len(messages),
336                           "Did not receive the expected number of messages")
337
338         audit = messages[1]["dsdbChange"]
339         self.assertEquals("Delete", audit["operation"])
340         self.assertFalse(audit["performedAsSystem"])
341         self.assertTrue(dn.lower(), audit["dn"].lower())
342         self.assertRegexpMatches(audit["remoteAddress"],
343                                  self.remoteAddress)
344         self.assertTrue(self.is_guid(audit["sessionId"]))
345         session_id = self.get_session()
346         self.assertEquals(session_id, audit["sessionId"])
347         service_description = self.get_service_description()
348         self.assertEquals(service_description, "LDAP")
349
350     def test_net_set_password_user_without_permission(self):
351
352         self.ldb.newuser(SECOND_USER_NAME, SECOND_USER_PASS)
353
354         creds = self.insta_creds(
355             template=self.get_credentials(),
356             username=SECOND_USER_NAME,
357             userpass=SECOND_USER_PASS,
358             kerberos_state=None)
359
360         lp = self.get_loadparm()
361         net = Net(creds, lp, server=self.server)
362         password = "newPassword!!42"
363         domain = lp.get("workgroup")
364
365         #
366         # This operation should fail and trigger a transaction roll back.
367         #
368         try:
369             net.set_password(newpassword=password.encode('utf-8'),
370                              account_name=USER_NAME,
371                              domain_name=domain)
372             self.fail("Expected exception not thrown")
373         except Exception:
374             pass
375
376         message = self.waitForTransaction(net)
377
378         audit = message["dsdbTransaction"]
379         self.assertEquals("rollback", audit["action"])
380         self.assertTrue(self.is_guid(audit["transactionId"]))
381
382     def test_create_and_delete_secret_over_lsa(self):
383
384         dn = "cn=Test Secret,CN=System," + self.base_dn
385         self.discardSetupMessages(dn)
386
387         creds = self.insta_creds(template=self.get_credentials())
388         lsa_conn = lsa.lsarpc(
389             "ncacn_np:%s" % self.server,
390             self.get_loadparm(),
391             creds)
392         lsa_handle = lsa_conn.OpenPolicy2(
393             system_name="\\",
394             attr=lsa.ObjectAttribute(),
395             access_mask=security.SEC_FLAG_MAXIMUM_ALLOWED)
396         secret_name = lsa.String()
397         secret_name.string = "G$Test"
398         lsa_conn.CreateSecret(
399             handle=lsa_handle,
400             name=secret_name,
401             access_mask=security.SEC_FLAG_MAXIMUM_ALLOWED)
402
403         messages = self.waitForMessages(1, dn=dn)
404         print("Received %d messages" % len(messages))
405         self.assertEquals(1,
406                           len(messages),
407                           "Did not receive the expected number of messages")
408
409         audit = messages[0]["dsdbChange"]
410         self.assertEquals("Add", audit["operation"])
411         self.assertTrue(audit["performedAsSystem"])
412         self.assertTrue(dn.lower(), audit["dn"].lower())
413         self.assertRegexpMatches(audit["remoteAddress"],
414                                  self.remoteAddress)
415         self.assertTrue(self.is_guid(audit["sessionId"]))
416         session_id = self.get_session()
417         self.assertEquals(session_id, audit["sessionId"])
418         service_description = self.get_service_description()
419         self.assertEquals(service_description, "DCE/RPC")
420         attributes = audit["attributes"]
421         self.assertEquals(2, len(attributes))
422
423         object_class = attributes["objectClass"]
424         self.assertEquals(1, len(object_class["actions"]))
425         action = object_class["actions"][0]
426         self.assertEquals("add", action["action"])
427         values = action["values"]
428         self.assertEquals(1, len(values))
429         self.assertEquals("secret", values[0]["value"])
430
431         cn = attributes["cn"]
432         self.assertEquals(1, len(cn["actions"]))
433         action = cn["actions"][0]
434         self.assertEquals("add", action["action"])
435         values = action["values"]
436         self.assertEquals(1, len(values))
437         self.assertEquals("Test Secret", values[0]["value"])
438
439         #
440         # Now delete the secret.
441         self.discardMessages()
442         h = lsa_conn.OpenSecret(
443             handle=lsa_handle,
444             name=secret_name,
445             access_mask=security.SEC_FLAG_MAXIMUM_ALLOWED)
446
447         lsa_conn.DeleteObject(h)
448         messages = self.waitForMessages(1, dn=dn)
449         print("Received %d messages" % len(messages))
450         self.assertEquals(1,
451                           len(messages),
452                           "Did not receive the expected number of messages")
453
454         dn = "cn=Test Secret,CN=System," + self.base_dn
455         audit = messages[0]["dsdbChange"]
456         self.assertEquals("Delete", audit["operation"])
457         self.assertTrue(audit["performedAsSystem"])
458         self.assertTrue(dn.lower(), audit["dn"].lower())
459         self.assertRegexpMatches(audit["remoteAddress"],
460                                  self.remoteAddress)
461         self.assertTrue(self.is_guid(audit["sessionId"]))
462         session_id = self.get_session()
463         self.assertEquals(session_id, audit["sessionId"])
464         service_description = self.get_service_description()
465         self.assertEquals(service_description, "DCE/RPC")
466
467     def test_modify(self):
468
469         dn = "cn=" + USER_NAME + ",cn=users," + self.base_dn
470         self.discardSetupMessages(dn)
471
472         #
473         # Add an attribute value
474         #
475         self.ldb.modify_ldif(
476             "dn: " + dn + "\n" +
477             "changetype: modify\n" +
478             "add: carLicense\n" +
479             "carLicense: license-01\n")
480
481         messages = self.waitForMessages(1, dn=dn)
482         print("Received %d messages" % len(messages))
483         self.assertEquals(1,
484                           len(messages),
485                           "Did not receive the expected number of messages")
486
487         audit = messages[0]["dsdbChange"]
488         self.assertEquals("Modify", audit["operation"])
489         self.assertFalse(audit["performedAsSystem"])
490         self.assertEquals(dn, audit["dn"])
491         self.assertRegexpMatches(audit["remoteAddress"],
492                                  self.remoteAddress)
493         self.assertTrue(self.is_guid(audit["sessionId"]))
494         session_id = self.get_session()
495         self.assertEquals(session_id, audit["sessionId"])
496         service_description = self.get_service_description()
497         self.assertEquals(service_description, "LDAP")
498
499         attributes = audit["attributes"]
500         self.assertEquals(1, len(attributes))
501         actions = attributes["carLicense"]["actions"]
502         self.assertEquals(1, len(actions))
503         self.assertEquals("add", actions[0]["action"])
504         values = actions[0]["values"]
505         self.assertEquals(1, len(values))
506         self.assertEquals("license-01", values[0]["value"])
507
508         #
509         # Add an another value to the attribute
510         #
511         self.discardMessages()
512         self.ldb.modify_ldif(
513             "dn: " + dn + "\n" +
514             "changetype: modify\n" +
515             "add: carLicense\n" +
516             "carLicense: license-02\n")
517
518         messages = self.waitForMessages(1, dn=dn)
519         print("Received %d messages" % len(messages))
520         self.assertEquals(1,
521                           len(messages),
522                           "Did not receive the expected number of messages")
523         attributes = messages[0]["dsdbChange"]["attributes"]
524         self.assertEquals(1, len(attributes))
525         actions = attributes["carLicense"]["actions"]
526         self.assertEquals(1, len(actions))
527         self.assertEquals("add", actions[0]["action"])
528         values = actions[0]["values"]
529         self.assertEquals(1, len(values))
530         self.assertEquals("license-02", values[0]["value"])
531
532         #
533         # Add an another two values to the attribute
534         #
535         self.discardMessages()
536         self.ldb.modify_ldif(
537             "dn: " + dn + "\n" +
538             "changetype: modify\n" +
539             "add: carLicense\n" +
540             "carLicense: license-03\n" +
541             "carLicense: license-04\n")
542
543         messages = self.waitForMessages(1, dn=dn)
544         print("Received %d messages" % len(messages))
545         self.assertEquals(1,
546                           len(messages),
547                           "Did not receive the expected number of messages")
548         attributes = messages[0]["dsdbChange"]["attributes"]
549         self.assertEquals(1, len(attributes))
550         actions = attributes["carLicense"]["actions"]
551         self.assertEquals(1, len(actions))
552         self.assertEquals("add", actions[0]["action"])
553         values = actions[0]["values"]
554         self.assertEquals(2, len(values))
555         self.assertEquals("license-03", values[0]["value"])
556         self.assertEquals("license-04", values[1]["value"])
557
558         #
559         # delete two values to the attribute
560         #
561         self.discardMessages()
562         self.ldb.modify_ldif(
563             "dn: " + dn + "\n" +
564             "changetype: delete\n" +
565             "delete: carLicense\n" +
566             "carLicense: license-03\n" +
567             "carLicense: license-04\n")
568
569         messages = self.waitForMessages(1, dn=dn)
570         print("Received %d messages" % len(messages))
571         self.assertEquals(1,
572                           len(messages),
573                           "Did not receive the expected number of messages")
574         attributes = messages[0]["dsdbChange"]["attributes"]
575         self.assertEquals(1, len(attributes))
576         actions = attributes["carLicense"]["actions"]
577         self.assertEquals(1, len(actions))
578         self.assertEquals("delete", actions[0]["action"])
579         values = actions[0]["values"]
580         self.assertEquals(2, len(values))
581         self.assertEquals("license-03", values[0]["value"])
582         self.assertEquals("license-04", values[1]["value"])
583
584         #
585         # replace two values to the attribute
586         #
587         self.discardMessages()
588         self.ldb.modify_ldif(
589             "dn: " + dn + "\n" +
590             "changetype: delete\n" +
591             "replace: carLicense\n" +
592             "carLicense: license-05\n" +
593             "carLicense: license-06\n")
594
595         messages = self.waitForMessages(1, dn=dn)
596         print("Received %d messages" % len(messages))
597         self.assertEquals(1,
598                           len(messages),
599                           "Did not receive the expected number of messages")
600         attributes = messages[0]["dsdbChange"]["attributes"]
601         self.assertEquals(1, len(attributes))
602         actions = attributes["carLicense"]["actions"]
603         self.assertEquals(1, len(actions))
604         self.assertEquals("replace", actions[0]["action"])
605         values = actions[0]["values"]
606         self.assertEquals(2, len(values))
607         self.assertEquals("license-05", values[0]["value"])
608         self.assertEquals("license-06", values[1]["value"])