python:tests: Remove unused imports
[samba.git] / python / samba / tests / samba_tool / user_getpassword_gmsa.py
1 # Unix SMB/CIFS implementation.
2 #
3 # Blackbox tests for reading Group Managed Service Account passwords
4 #
5 # Copyright (C) Catalyst.Net Ltd. 2023
6 #
7 # Written by Rob van der Linde <rob@catalyst.net.nz>
8 #
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.
13 #
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.
18 #
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/>.
21 #
22
23 import sys
24 import os
25
26 sys.path.insert(0, "bin/python")
27 os.environ["PYTHONUNBUFFERED"] = "1"
28
29 from ldb import SCOPE_BASE
30
31 from samba.credentials import MUST_USE_KERBEROS
32 from samba.dcerpc import security, samr
33 from samba.dsdb import UF_WORKSTATION_TRUST_ACCOUNT
34 from samba.netcmd.domain.models import User
35 from samba.ndr import ndr_pack, ndr_unpack
36 from samba.tests import connect_samdb, delete_force
37
38 from samba.tests import BlackboxTestCase
39
40 DC_SERVER = os.environ["SERVER"]
41 SERVER = os.environ["SERVER"]
42 SERVER_USERNAME = os.environ["USERNAME"]
43 SERVER_PASSWORD = os.environ["PASSWORD"]
44
45 HOST = f"ldap://{SERVER}"
46 CREDS = f"-U{SERVER_USERNAME}%{SERVER_PASSWORD}"
47
48
49 class GMSAPasswordTest(BlackboxTestCase):
50     """Blackbox tests for GMSA getpassword and connecting as that user."""
51
52     @classmethod
53     def setUpClass(cls):
54         cls.lp = cls.get_loadparm()
55         cls.env_creds = cls.get_env_credentials(lp=cls.lp,
56                                                 env_username="USERNAME",
57                                                 env_password="PASSWORD",
58                                                 env_domain="DOMAIN",
59                                                 env_realm="REALM")
60         cls.samdb = connect_samdb(HOST, lp=cls.lp, credentials=cls.env_creds)
61         super().setUpClass()
62
63     @classmethod
64     def setUpTestData(cls):
65         cls.username = "GMSA_Test_User$"
66         cls.base_dn = f"CN=Managed Service Accounts,{cls.samdb.domain_dn()}"
67         cls.user_dn = f"CN={cls.username},{cls.base_dn}"
68
69         msg = cls.samdb.search(base="", scope=SCOPE_BASE, attrs=["tokenGroups"])[0]
70         connecting_user_sid = str(ndr_unpack(security.dom_sid, msg["tokenGroups"][0]))
71
72         domain_sid = security.dom_sid(cls.samdb.get_domain_sid())
73         allow_sddl = f"O:SYD:(A;;RP;;;{connecting_user_sid})"
74         allow_sd = ndr_pack(security.descriptor.from_sddl(allow_sddl, domain_sid))
75
76         details = {
77             "dn": str(cls.user_dn),
78             "objectClass": "msDS-GroupManagedServiceAccount",
79             "msDS-ManagedPasswordInterval": "1",
80             "msDS-GroupMSAMembership": allow_sd,
81             "sAMAccountName": cls.username,
82             "userAccountControl": str(UF_WORKSTATION_TRUST_ACCOUNT),
83         }
84
85         cls.samdb.add(details)
86         cls.addClassCleanup(delete_force, cls.samdb, cls.user_dn)
87
88         cls.user = User.get(cls.samdb, username=cls.username)
89
90     def getpassword(self, attrs):
91         cmd = f"user getpassword --attributes={attrs} {self.username}"
92
93         ldif = self.check_output(cmd).decode()
94         res = self.samdb.parse_ldif(ldif)
95         _, user_message = next(res)
96
97         # check each attr is returned
98         for attr in attrs.split(","):
99             self.assertIn(attr, user_message)
100
101         return user_message
102
103     def test_getpassword(self):
104         self.getpassword("virtualClearTextUTF16,unicodePwd")
105         self.getpassword("virtualClearTextUTF16")
106         self.getpassword("unicodePwd")
107
108     def test_utf16_password(self):
109         user_msg = self.getpassword("virtualClearTextUTF16")
110         password = user_msg["virtualClearTextUTF16"][0]
111
112         creds = self.insta_creds(template=self.env_creds)
113         creds.set_username(self.username)
114         creds.set_utf16_password(password)
115         db = connect_samdb(HOST, credentials=creds, lp=self.lp)
116
117         msg = db.search(base="", scope=SCOPE_BASE, attrs=["tokenGroups"])[0]
118         connecting_user_sid = str(ndr_unpack(security.dom_sid, msg["tokenGroups"][0]))
119
120         self.assertEqual(self.user.object_sid, connecting_user_sid)
121
122     def test_utf8_password(self):
123         user_msg = self.getpassword("virtualClearTextUTF8")
124         password = str(user_msg["virtualClearTextUTF8"][0])
125
126         creds = self.insta_creds(template=self.env_creds)
127         # Because the password has been converted to utf-8 via UTF16_MUNGED
128         # the nthash is no longer valid. We need to use AES kerberos ciphers
129         # for this to work.
130         creds.set_kerberos_state(MUST_USE_KERBEROS)
131         creds.set_username(self.username)
132         creds.set_password(password)
133         db = connect_samdb(HOST, credentials=creds, lp=self.lp)
134
135         msg = db.search(base="", scope=SCOPE_BASE, attrs=["tokenGroups"])[0]
136         connecting_user_sid = str(ndr_unpack(security.dom_sid, msg["tokenGroups"][0]))
137
138         self.assertEqual(self.user.object_sid, connecting_user_sid)
139
140     def test_unicode_pwd(self):
141         user_msg = self.getpassword("unicodePwd")
142
143         creds = self.insta_creds(template=self.env_creds)
144         creds.set_username(self.username)
145         nt_pass = samr.Password()
146         nt_pass.hash = list(user_msg["unicodePwd"][0])
147         creds.set_nt_hash(nt_pass)
148         db = connect_samdb(HOST, credentials=creds, lp=self.lp)
149
150         msg = db.search(base="", scope=SCOPE_BASE, attrs=["tokenGroups"])[0]
151         connecting_user_sid = str(ndr_unpack(security.dom_sid, msg["tokenGroups"][0]))
152
153         self.assertEqual(self.user.object_sid, connecting_user_sid)
154
155     @classmethod
156     def _make_cmdline(cls, line):
157         """Override to pass line as samba-tool subcommand instead.
158
159         Automatically fills in HOST and CREDS as well.
160         """
161         if isinstance(line, list):
162             cmd = ["samba-tool"] + line + ["-H", SERVER, CREDS]
163         else:
164             cmd = f"samba-tool {line} -H {HOST} {CREDS}"
165
166         return super()._make_cmdline(cmd)
167
168
169 if __name__ == "__main__":
170     import unittest
171     unittest.main()