python:tests: Fix typo
[Samba.git] / python / samba / tests / samba_tool / service_account.py
blobe16264fa4ab4781e58b20b067b10816bd227c399
1 # Unix SMB/CIFS implementation.
3 # Tests for samba-tool service-account commands.
5 # Copyright (C) Catalyst.Net Ltd. 2024
7 # Written by Rob van der Linde <rob@catalyst.net.nz>
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 3 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 import json
24 import os
26 from samba.domain.models import Group, GroupManagedServiceAccount, User
27 from samba.domain.models.constants import GROUP_MSA_MEMBERSHIP_DEFAULT
29 from .base import SambaToolCmdTest
31 HOST = "ldap://{DC_SERVER}".format(**os.environ)
32 CREDS = "-U{DC_USERNAME}%{DC_PASSWORD}".format(**os.environ)
35 class ServiceAccountTests(SambaToolCmdTest):
37 @classmethod
38 def setUpClass(cls):
39 cls.samdb = cls.getSamDB("-H", HOST, CREDS)
40 super().setUpClass()
42 @classmethod
43 def setUpTestData(cls):
44 """Setup initial data without the samba-tool command."""
45 cls.accounts = [
46 GroupManagedServiceAccount.create(cls.samdb, name="foo",
47 dns_host_name="example.com"),
48 GroupManagedServiceAccount.create(cls.samdb, name="bar",
49 dns_host_name="example.com"),
50 GroupManagedServiceAccount.create(cls.samdb, name="baz",
51 dns_host_name="example.com"),
54 for account in cls.accounts:
55 cls.addClassCleanup(account.delete, cls.samdb)
57 @classmethod
58 def _run(cls, *argv):
59 """Override _run, so we don't always have to pass HOST and CREDS."""
60 args = list(argv)
61 args.extend(["-H", HOST, CREDS])
62 return super()._run(*args)
64 runcmd = _run
65 runsubcmd = _run
67 @classmethod
68 def delete_service_account(cls, name):
69 """Delete a service account using samba-tool."""
70 result, out, err = cls.runcmd("service-account", "delete",
71 "--name", name)
72 assert result is None
73 assert out.startswith("Deleted group managed service account")
75 @classmethod
76 def create_service_account(cls, name, dns_host_name="example.com",
77 managed_password_interval=None):
78 """Create a service account using samba-tool.
80 Adds a class cleanup to automatically delete the gmsa at the end
81 of the test case.
82 """
83 # required arguments
84 cmd = ["service-account", "create",
85 "--name", name,
86 "--dns-host-name", dns_host_name]
88 # defaults to 30 if left None
89 if managed_password_interval is not None:
90 cmd += ["--managed-password-interval", str(managed_password_interval)]
92 # create gmsa and setup cleanup
93 result, out, err = cls.runcmd(*cmd)
94 assert result is None
95 assert out.startswith("Created group managed service account")
96 cls.addClassCleanup(cls.delete_service_account, name=name)
98 def test_list(self):
99 """List group managed service accounts with samba-tool."""
100 result, out, err = self.runcmd("service-account", "list")
101 self.assertIsNone(result, msg=err)
103 self.assertIn("foo$", out)
104 self.assertIn("bar$", out)
105 self.assertIn("baz$", out)
107 def test_list__json(self):
108 """List group managed service accounts in json format."""
109 result, out, err = self.runcmd("service-account", "list", "--json")
110 self.assertIsNone(result, msg=err)
111 accounts = json.loads(out)
113 self.assertIn("foo$", accounts)
114 self.assertIn("bar$", accounts)
115 self.assertIn("baz$", accounts)
117 def test_create(self):
118 """Create a group managed service account using samba-tool."""
119 # Create a Group Managed Service account using samba-tool.
120 name = self.unique_name()
121 self.create_service_account(name,
122 dns_host_name="test.com",
123 managed_password_interval=60)
125 # Group Managed Service count exists.
126 # Since GroupManagedServiceAccount is also a Computer it ends in '$'
127 gmsa = GroupManagedServiceAccount.get(self.samdb, account_name=name + "$")
128 self.assertIsNotNone(gmsa)
129 self.assertEqual(gmsa.account_name, name + "$")
130 self.assertEqual(gmsa.dns_host_name, "test.com")
131 self.assertEqual(gmsa.managed_password_interval, 60)
133 def test_view(self):
134 """View a group managed service account using samba-tool."""
135 result, out, err = self.runcmd("service-account", "view",
136 "--name", "foo")
137 self.assertIsNone(result, msg=err)
139 # Service account view always returns JSON.
140 response = json.loads(out)
141 self.assertEqual(response["cn"], "foo")
142 self.assertEqual(response["dNSHostName"], "example.com")
143 self.assertEqual(response["msDS-ManagedPasswordInterval"], 30)
145 def test_delete(self):
146 """Delete a group managed service account using samba-tool."""
147 # Create the gmsa without samba-tool.
148 name = self.unique_name()
149 GroupManagedServiceAccount.create(self.samdb, name=name,
150 dns_host_name="example.com"),
152 # The group managed service account exists.
153 gmsa = GroupManagedServiceAccount.get(self.samdb, account_name=name + "$")
154 self.assertIsNotNone(gmsa)
156 # Now delete the gmsa.
157 result, out, err = self.runcmd("service-account", "delete",
158 "--name", name)
159 self.assertIsNone(result, msg=err)
161 # Service account is gone.
162 gmsa = GroupManagedServiceAccount.get(self.samdb, account_name=name + "$")
163 self.assertIsNone(gmsa, msg="Group Managed Service Account not deleted.")
165 def test_modify(self):
166 """Modify a group managed service account and manually set SDDL."""
167 name = self.unique_name()
168 gmsa = GroupManagedServiceAccount.create(self.samdb, name=name,
169 dns_host_name="example.com")
170 self.addCleanup(gmsa.delete, self.samdb)
172 # Build some SDDL for adding a user manually.
173 bob = User.get(self.samdb, account_name="bob")
174 sddl = gmsa.group_msa_membership.as_sddl()
175 sddl += f"(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;{bob.object_sid})"
177 result, out, err = self.runcmd("service-account", "modify",
178 "--name", name,
179 "--dns-host-name", "new.example.com",
180 "--group-msa-membership", sddl)
181 self.assertIsNone(result, msg=err)
183 # Check field changes and see if the new user is in there.
184 gmsa = GroupManagedServiceAccount.get(self.samdb, account_name=name + "$")
185 self.assertEqual(gmsa.dns_host_name, "new.example.com")
186 self.assertIn(bob.object_sid, gmsa.trustees)
189 class ServiceAccountGroupMSAMembershipTests(SambaToolCmdTest):
191 @classmethod
192 def setUpClass(cls):
193 cls.samdb = cls.getSamDB("-H", HOST, CREDS)
194 super().setUpClass()
196 @classmethod
197 def setUpTestData(cls):
198 """Setup initial data without the samba-tool command."""
199 # Add a user other than the Administrator to the default SDDL.
200 jane = User.get(cls.samdb, account_name="jane")
201 sddl = f"{GROUP_MSA_MEMBERSHIP_DEFAULT}(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;{jane.object_sid})"
202 cls.gmsa = GroupManagedServiceAccount.create(cls.samdb, name="gmsa",
203 dns_host_name="example.com",
204 group_msa_membership=sddl)
206 cls.addClassCleanup(cls.gmsa.delete, cls.samdb)
208 @classmethod
209 def _run(cls, *argv):
210 """Override _run, so we don't always have to pass HOST and CREDS."""
211 args = list(argv)
212 args.extend(["-H", HOST, CREDS])
213 return super()._run(*args)
215 runcmd = _run
216 runsubcmd = _run
218 def test_show(self):
219 """Show password viewers on a Group Managed Service Account."""
220 result, out, err = self.runcmd("service-account",
221 "group-msa-membership", "show",
222 "--name", self.gmsa.account_name)
223 self.assertIsNone(result, msg=err)
225 # Plain text output.
226 self.assertIn(
227 "Account-DN: CN=gmsa,CN=Managed Service Accounts,DC=addom,DC=samba,DC=example,DC=com", out)
228 self.assertIn(
229 "CN=Administrator,CN=Users,DC=addom,DC=samba,DC=example,DC=com", out)
230 self.assertIn(
231 "CN=jane,CN=Users,DC=addom,DC=samba,DC=example,DC=com", out)
233 def test_show__json(self):
234 """Show password viewers on a Group Managed Service Account as JSON."""
235 result, out, err = self.runcmd("service-account",
236 "group-msa-membership", "show",
237 "--name", self.gmsa.account_name,
238 "--json")
239 self.assertIsNone(result, msg=err)
241 # JSON output.
242 response = json.loads(out)
243 self.assertEqual(response["dn"], str(self.gmsa.dn))
244 self.assertListEqual(response["trustees"], [
245 "CN=Administrator,CN=Users,DC=addom,DC=samba,DC=example,DC=com",
246 "CN=jane,CN=Users,DC=addom,DC=samba,DC=example,DC=com"
249 def test_add__username(self):
250 """Add principal to a Group Managed Service Account by username."""
251 alice = User.get(self.samdb, account_name="alice")
252 name = self.unique_name()
253 gmsa = GroupManagedServiceAccount.create(self.samdb, name=name,
254 dns_host_name="example.com")
255 self.addCleanup(gmsa.delete, self.samdb)
257 # Add user 'alice' by username.
258 result, out, err = self.runcmd("service-account",
259 "group-msa-membership", "add",
260 "--name", gmsa.account_name,
261 "--principal", alice.account_name)
262 self.assertIsNone(result, msg=err)
264 # See if user was added.
265 gmsa.refresh(self.samdb)
266 self.assertIn(alice.object_sid, gmsa.trustees)
268 def test_add__dn(self):
269 """Add principal to a Group Managed Service Account by dn."""
270 admins = Group.get(self.samdb, name="DnsAdmins")
271 name = self.unique_name()
272 gmsa = GroupManagedServiceAccount.create(self.samdb, name=name,
273 dns_host_name="example.com")
274 self.addCleanup(gmsa.delete, self.samdb)
276 # Add group 'DnsAdmins' by dn.
277 result, out, err = self.runcmd("service-account",
278 "group-msa-membership", "add",
279 "--name", gmsa.account_name,
280 "--principal", str(admins.dn))
281 self.assertIsNone(result, msg=err)
283 # See if group was added.
284 gmsa.refresh(self.samdb)
285 self.assertIn(admins.object_sid, gmsa.trustees)
287 def test_remove__username(self):
288 """Remove principal from a Group Managed Service Account by username."""
289 # Create a GMSA with custom SDDL and add extra user.
290 bob = User.get(self.samdb, account_name="bob")
291 sddl = f"{GROUP_MSA_MEMBERSHIP_DEFAULT}(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;{bob.object_sid})"
292 name = self.unique_name()
293 gmsa = GroupManagedServiceAccount.create(self.samdb, name=name,
294 dns_host_name="example.com",
295 group_msa_membership=sddl)
297 # The user is in list to start with.
298 self.assertIn(bob.object_sid, gmsa.trustees)
300 # Remove user 'bob' by username.
301 result, out, err = self.runcmd("service-account",
302 "group-msa-membership", "remove",
303 "--name", gmsa.account_name,
304 "--principal", bob.account_name)
305 self.assertIsNone(result, msg=err)
307 # See if user was removed.
308 gmsa.refresh(self.samdb)
309 self.assertNotIn(bob.object_sid, gmsa.trustees)
311 def test_remove__dn(self):
312 """Remove principal from a Group Managed Service Account by dn."""
313 # Create a GMSA with custom SDDL and add extra group.
314 admins = Group.get(self.samdb, name="DnsAdmins")
315 sddl = f"{GROUP_MSA_MEMBERSHIP_DEFAULT}(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;{admins.object_sid})"
316 name = self.unique_name()
317 gmsa = GroupManagedServiceAccount.create(self.samdb, name=name,
318 dns_host_name="example.com",
319 group_msa_membership=sddl)
321 # The group is in list to start with.
322 self.assertIn(admins.object_sid, gmsa.trustees)
324 # Remove group 'DnsAdmins' by dn.
325 result, out, err = self.runcmd("service-account",
326 "group-msa-membership", "remove",
327 "--name", gmsa.account_name,
328 "--principal", str(admins.dn))
329 self.assertIsNone(result, msg=err)
331 # See if group was removed.
332 gmsa.refresh(self.samdb)
333 self.assertNotIn(admins.object_sid, gmsa.trustees)