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/>.
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
):
39 cls
.samdb
= cls
.getSamDB("-H", HOST
, CREDS
)
43 def setUpTestData(cls
):
44 """Setup initial data without the samba-tool command."""
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
)
59 """Override _run, so we don't always have to pass HOST and CREDS."""
61 args
.extend(["-H", HOST
, CREDS
])
62 return super()._run
(*args
)
68 def delete_service_account(cls
, name
):
69 """Delete a service account using samba-tool."""
70 result
, out
, err
= cls
.runcmd("service-account", "delete",
73 assert out
.startswith("Deleted group managed service account")
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
84 cmd
= ["service-account", "create",
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
)
95 assert out
.startswith("Created group managed service account")
96 cls
.addClassCleanup(cls
.delete_service_account
, name
=name
)
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)
134 """View a group managed service account using samba-tool."""
135 result
, out
, err
= self
.runcmd("service-account", "view",
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",
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",
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
):
193 cls
.samdb
= cls
.getSamDB("-H", HOST
, CREDS
)
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
)
209 def _run(cls
, *argv
):
210 """Override _run, so we don't always have to pass HOST and CREDS."""
212 args
.extend(["-H", HOST
, CREDS
])
213 return super()._run
(*args
)
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
)
227 "Account-DN: CN=gmsa,CN=Managed Service Accounts,DC=addom,DC=samba,DC=example,DC=com", out
)
229 "CN=Administrator,CN=Users,DC=addom,DC=samba,DC=example,DC=com", out
)
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
,
239 self
.assertIsNone(result
, msg
=err
)
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
)