The branch, master has been updated via f1a72fc63df samba-tool: Add a gpo command for removing VGP Host Access Group Policy via 90acb3cf991 samba-tool: Test gpo manage access remove command via 482046c56ba samba-tool: Add a gpo command for adding VGP Host Access Group Policy via 996a0bd2e46 samba-tool: Test gpo manage access add command via 3f3c2b5b338 samba-tool: Add a gpo command for listing VGP Host Access Group Policy via 76868b50f36 samba-tool: Test gpo manage access list command via a6cb5b8cc57 gpo: Apply Group Policy Host Access configuration from VGP via de3dbfda9c5 gpo: Test Group Policy Host Access Configuration for VGP from 591c9196962 smbd: free open_rec state in remove_deferred_open_message_smb2_internal()
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit f1a72fc63dfe425c9e5ed9357ecf8dfe340976c1 Author: David Mulder <dmul...@suse.com> Date: Wed Mar 3 14:19:01 2021 -0700 samba-tool: Add a gpo command for removing VGP Host Access Group Policy Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> Autobuild-User(master): Jeremy Allison <j...@samba.org> Autobuild-Date(master): Thu Mar 18 20:02:50 UTC 2021 on sn-devel-184 commit 90acb3cf99125f53abd2efceaf04a6f63b796a84 Author: David Mulder <dmul...@suse.com> Date: Wed Mar 3 12:28:07 2021 -0700 samba-tool: Test gpo manage access remove command Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit 482046c56ba2bbc4a464bbc518877501598e4e31 Author: David Mulder <dmul...@suse.com> Date: Tue Mar 2 15:05:46 2021 -0700 samba-tool: Add a gpo command for adding VGP Host Access Group Policy Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit 996a0bd2e46fa9e164f390561c64dd5d954eb7e2 Author: David Mulder <dmul...@suse.com> Date: Mon Mar 1 10:31:54 2021 -0700 samba-tool: Test gpo manage access add command Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit 3f3c2b5b33864ac162ed578279244be0182c42f0 Author: David Mulder <dmul...@suse.com> Date: Wed Feb 24 06:36:45 2021 -0700 samba-tool: Add a gpo command for listing VGP Host Access Group Policy Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit 76868b50f3689107e101511322dbf749c26d8342 Author: David Mulder <dmul...@suse.com> Date: Tue Feb 23 13:12:09 2021 -0700 samba-tool: Test gpo manage access list command Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit a6cb5b8cc57a0252f27ec83645bff3791ec9aab6 Author: David Mulder <dmul...@suse.com> Date: Tue Feb 23 11:12:05 2021 -0700 gpo: Apply Group Policy Host Access configuration from VGP Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit de3dbfda9c513bc6fdf4bf38c40e72d4f278e7e0 Author: David Mulder <dmul...@suse.com> Date: Mon Feb 22 15:01:04 2021 -0700 gpo: Test Group Policy Host Access Configuration for VGP Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> ----------------------------------------------------------------------- Summary of changes: docs-xml/manpages/samba-tool.8.xml | 15 ++ python/samba/netcmd/gpo.py | 330 ++++++++++++++++++++++++++++++ python/samba/tests/gpo.py | 131 ++++++++++++ python/samba/tests/samba_tool/gpo_exts.py | 202 ++++++++++++++++++ python/samba/vgp_access_ext.py | 133 ++++++++++++ source4/scripting/bin/samba-gpupdate | 2 + source4/selftest/tests.py | 2 + 7 files changed, 815 insertions(+) create mode 100644 python/samba/tests/samba_tool/gpo_exts.py create mode 100644 python/samba/vgp_access_ext.py Changeset truncated at 500 lines: diff --git a/docs-xml/manpages/samba-tool.8.xml b/docs-xml/manpages/samba-tool.8.xml index 346143ddafe..beef6cfb265 100644 --- a/docs-xml/manpages/samba-tool.8.xml +++ b/docs-xml/manpages/samba-tool.8.xml @@ -974,6 +974,21 @@ <para>Sets a VGP Issue Group Policy to the sysvol</para> </refsect3> +<refsect3> + <title>gpo manage access add</title> + <para>Adds a VGP Host Access Group Policy to the sysvol</para> +</refsect3> + +<refsect3> + <title>gpo manage access list</title> + <para>List VGP Host Access Group Policy from the sysvol</para> +</refsect3> + +<refsect3> + <title>gpo manage access remove</title> + <para>Remove a VGP Host Access Group Policy from the sysvol</para> +</refsect3> + <refsect2> <title>group</title> <para>Manage groups.</para> diff --git a/python/samba/netcmd/gpo.py b/python/samba/netcmd/gpo.py index 1b4159c4c0c..bd2db9b1ab2 100644 --- a/python/samba/netcmd/gpo.py +++ b/python/samba/netcmd/gpo.py @@ -3659,6 +3659,335 @@ class cmd_issue(SuperCommand): subcommands["list"] = cmd_list_issue() subcommands["set"] = cmd_set_issue() +class cmd_list_access(Command): + """List VGP Host Access Group Policy from the sysvol + +This command lists host access rules from the sysvol that will be applied to winbind clients. + +Example: +samba-tool gpo manage access list {31B2F340-016D-11D2-945F-00C04FB984F9} + """ + + synopsis = "%prog <gpo> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + ] + + takes_args = ["gpo"] + + def run(self, gpo, H=None, sambaopts=None, credopts=None, versionopts=None): + self.lp = sambaopts.get_loadparm() + self.creds = credopts.get_credentials(self.lp, fallback_machine=True) + + # We need to know writable DC to setup SMB connection + if H and H.startswith('ldap://'): + dc_hostname = H[7:] + self.url = H + else: + dc_hostname = netcmd_finddc(self.lp, self.creds) + self.url = dc_url(self.lp, self.creds, dc=dc_hostname) + + # SMB connect to DC + conn = smb_connection(dc_hostname, + 'sysvol', + lp=self.lp, + creds=self.creds) + + realm = self.lp.get('realm') + vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo, + 'MACHINE\\VGP\\VTLA\\VAS' + 'HostAccessControl\\Allow\\manifest.xml']) + try: + allow = ET.fromstring(conn.loadfile(vgp_xml)) + except NTSTATUSError as e: + # STATUS_OBJECT_NAME_INVALID, STATUS_OBJECT_NAME_NOT_FOUND, + # STATUS_OBJECT_PATH_NOT_FOUND + if e.args[0] in [0xC0000033, 0xC0000034, 0xC000003A]: + allow = None # The file doesn't exist, ignore it + elif e.args[0] == 0xC0000022: # STATUS_ACCESS_DENIED + raise CommandError("The authenticated user does " + "not have sufficient privileges") + else: + raise + + if allow is not None: + policy = allow.find('policysetting') + data = policy.find('data') + for listelement in data.findall('listelement'): + adobject = listelement.find('adobject') + name = adobject.find('name') + domain = adobject.find('domain') + self.outf.write('+:%s\\%s:ALL\n' % (domain.text, name.text)) + + vgp_xml = '\\'.join([realm.lower(), 'Policies', gpo, + 'MACHINE\\VGP\\VTLA\\VAS' + 'HostAccessControl\\Deny\\manifest.xml']) + try: + deny = ET.fromstring(conn.loadfile(vgp_xml)) + except NTSTATUSError as e: + # STATUS_OBJECT_NAME_INVALID, STATUS_OBJECT_NAME_NOT_FOUND, + # STATUS_OBJECT_PATH_NOT_FOUND + if e.args[0] in [0xC0000033, 0xC0000034, 0xC000003A]: + deny = None # The file doesn't exist, ignore it + elif e.args[0] == 0xC0000022: # STATUS_ACCESS_DENIED + raise CommandError("The authenticated user does " + "not have sufficient privileges") + else: + raise + + if deny is not None: + policy = deny.find('policysetting') + data = policy.find('data') + for listelement in data.findall('listelement'): + adobject = listelement.find('adobject') + name = adobject.find('name') + domain = adobject.find('domain') + self.outf.write('-:%s\\%s:ALL\n' % (domain.text, name.text)) + +class cmd_add_access(Command): + """Adds a VGP Host Access Group Policy to the sysvol + +This command adds a host access setting to the sysvol for applying to winbind +clients. + +Example: +samba-tool gpo manage access add {31B2F340-016D-11D2-945F-00C04FB984F9} allow goodguy example.com + """ + + synopsis = "%prog <gpo> <allow/deny> <cn> <domain> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + ] + + takes_args = ["gpo", "etype", "cn", "domain"] + + def run(self, gpo, etype, cn, domain, H=None, sambaopts=None, + credopts=None, versionopts=None): + self.lp = sambaopts.get_loadparm() + self.creds = credopts.get_credentials(self.lp, fallback_machine=True) + + # We need to know writable DC to setup SMB connection + if H and H.startswith('ldap://'): + dc_hostname = H[7:] + self.url = H + else: + dc_hostname = netcmd_finddc(self.lp, self.creds) + self.url = dc_url(self.lp, self.creds, dc=dc_hostname) + + # SMB connect to DC + conn = smb_connection(dc_hostname, + 'sysvol', + lp=self.lp, + creds=self.creds) + + realm = self.lp.get('realm') + if etype == 'allow': + vgp_dir = '\\'.join([realm.lower(), 'Policies', gpo, + 'MACHINE\\VGP\\VTLA\\VAS' + 'HostAccessControl\\Allow']) + elif etype == 'deny': + vgp_dir = '\\'.join([realm.lower(), 'Policies', gpo, + 'MACHINE\\VGP\\VTLA\\VAS' + 'HostAccessControl\\Deny']) + else: + raise CommandError("The entry type must be either 'allow' or " + "'deny'. Unknown type '%s'" % etype) + vgp_xml = '\\'.join([vgp_dir, 'manifest.xml']) + try: + xml_data = ET.ElementTree(ET.fromstring(conn.loadfile(vgp_xml))) + policy = xml_data.getroot().find('policysetting') + data = policy.find('data') + except NTSTATUSError as e: + # STATUS_OBJECT_NAME_INVALID, STATUS_OBJECT_NAME_NOT_FOUND, + # STATUS_OBJECT_PATH_NOT_FOUND + if e.args[0] in [0xC0000033, 0xC0000034, 0xC000003A]: + # The file doesn't exist, so create the xml structure + xml_data = ET.ElementTree(ET.Element('vgppolicy')) + policysetting = ET.SubElement(xml_data.getroot(), + 'policysetting') + pv = ET.SubElement(policysetting, 'version') + pv.text = '1' + name = ET.SubElement(policysetting, 'name') + name.text = 'Host Access Control' + description = ET.SubElement(policysetting, 'description') + description.text = 'Represents host access control data (pam_access)' + apply_mode = ET.SubElement(policysetting, 'apply_mode') + apply_mode.text = 'merge' + data = ET.SubElement(policysetting, 'data') + elif e.args[0] == 0xC0000022: # STATUS_ACCESS_DENIED + raise CommandError("The authenticated user does " + "not have sufficient privileges") + else: + raise + + url = dc_url(self.lp, self.creds, dc=domain) + samdb = SamDB(url=url, session_info=system_session(), + credentials=self.creds, lp=self.lp) + + res = samdb.search(base=samdb.domain_dn(), + scope=ldb.SCOPE_SUBTREE, + expression="(cn=%s)" % cn, + attrs=['userPrincipalName', + 'samaccountname', + 'objectClass']) + if len(res) == 0: + raise CommandError('Unable to find user or group "%s"' % cn) + + objectclass = get_string(res[0]['objectClass'][-1]) + if objectclass not in ['user', 'group']: + raise CommandError('%s is not a user or group' % cn) + + listelement = ET.SubElement(data, 'listelement') + etype = ET.SubElement(listelement, 'type') + etype.text = objectclass.upper() + entry = ET.SubElement(listelement, 'entry') + if objectclass == 'user': + entry.text = get_string(res[0]['userPrincipalName'][-1]) + else: + groupattr = ET.SubElement(data, 'groupattr') + groupattr.text = 'samAccountName' + entry.text = '%s\\%s' % (domain, + get_string(res[0]['samaccountname'][-1])) + adobject = ET.SubElement(listelement, 'adobject') + name = ET.SubElement(adobject, 'name') + name.text = get_string(res[0]['samaccountname'][-1]) + domain_elm = ET.SubElement(adobject, 'domain') + domain_elm.text = domain + etype = ET.SubElement(adobject, 'type') + etype.text = objectclass + + out = BytesIO() + xml_data.write(out, encoding='UTF-8', xml_declaration=True) + out.seek(0) + try: + create_directory_hier(conn, vgp_dir) + conn.savefile(vgp_xml, out.read()) + except NTSTATUSError as e: + if e.args[0] == 0xC0000022: # STATUS_ACCESS_DENIED + raise CommandError("The authenticated user does " + "not have sufficient privileges") + raise + +class cmd_remove_access(Command): + """Remove a VGP Host Access Group Policy from the sysvol + +This command removes a host access setting from the sysvol for applying to +winbind clients. + +Example: +samba-tool gpo manage access remove {31B2F340-016D-11D2-945F-00C04FB984F9} allow goodguy example.com + """ + + synopsis = "%prog <gpo> <allow/deny> <name> <domain> [options]" + + takes_optiongroups = { + "sambaopts": options.SambaOptions, + "versionopts": options.VersionOptions, + "credopts": options.CredentialsOptions, + } + + takes_options = [ + Option("-H", "--URL", help="LDB URL for database or target server", type=str, + metavar="URL", dest="H"), + ] + + takes_args = ["gpo", "etype", "name", "domain"] + + def run(self, gpo, etype, name, domain, H=None, sambaopts=None, + credopts=None, versionopts=None): + self.lp = sambaopts.get_loadparm() + self.creds = credopts.get_credentials(self.lp, fallback_machine=True) + + # We need to know writable DC to setup SMB connection + if H and H.startswith('ldap://'): + dc_hostname = H[7:] + self.url = H + else: + dc_hostname = netcmd_finddc(self.lp, self.creds) + self.url = dc_url(self.lp, self.creds, dc=dc_hostname) + + # SMB connect to DC + conn = smb_connection(dc_hostname, + 'sysvol', + lp=self.lp, + creds=self.creds) + + realm = self.lp.get('realm') + if etype == 'allow': + vgp_dir = '\\'.join([realm.lower(), 'Policies', gpo, + 'MACHINE\\VGP\\VTLA\\VAS' + 'HostAccessControl\\Allow']) + elif etype == 'deny': + vgp_dir = '\\'.join([realm.lower(), 'Policies', gpo, + 'MACHINE\\VGP\\VTLA\\VAS' + 'HostAccessControl\\Deny']) + else: + raise CommandError("The entry type must be either 'allow' or " + "'deny'. Unknown type '%s'" % etype) + vgp_xml = '\\'.join([vgp_dir, 'manifest.xml']) + try: + xml_data = ET.ElementTree(ET.fromstring(conn.loadfile(vgp_xml))) + policy = xml_data.getroot().find('policysetting') + data = policy.find('data') + except NTSTATUSError as e: + # STATUS_OBJECT_NAME_INVALID, STATUS_OBJECT_NAME_NOT_FOUND, + # STATUS_OBJECT_PATH_NOT_FOUND + if e.args[0] in [0xC0000033, 0xC0000034, 0xC000003A]: + raise CommandError("Cannot remove %s entry because it does " + "not exist" % etype) + elif e.args[0] == 0xC0000022: # STATUS_ACCESS_DENIED + raise CommandError("The authenticated user does " + "not have sufficient privileges") + else: + raise + + for listelement in data.findall('listelement'): + adobject = listelement.find('adobject') + name_elm = adobject.find('name') + domain_elm = adobject.find('domain') + if name_elm is not None and name_elm.text == name and \ + domain_elm is not None and domain_elm.text == domain: + data.remove(listelement) + break + else: + raise CommandError("Cannot remove %s entry because it does " + "not exist" % etype) + + out = BytesIO() + xml_data.write(out, encoding='UTF-8', xml_declaration=True) + out.seek(0) + try: + create_directory_hier(conn, vgp_dir) + conn.savefile(vgp_xml, out.read()) + except NTSTATUSError as e: + if e.args[0] == 0xC0000022: # STATUS_ACCESS_DENIED + raise CommandError("The authenticated user does " + "not have sufficient privileges") + raise + +class cmd_access(SuperCommand): + """Manage Host Access Group Policy Objects""" + subcommands = {} + subcommands["list"] = cmd_list_access() + subcommands["add"] = cmd_add_access() + subcommands["remove"] = cmd_remove_access() + class cmd_manage(SuperCommand): """Manage Group Policy Objects""" subcommands = {} @@ -3671,6 +4000,7 @@ class cmd_manage(SuperCommand): subcommands["scripts"] = cmd_scripts() subcommands["motd"] = cmd_motd() subcommands["issue"] = cmd_issue() + subcommands["access"] = cmd_access() class cmd_gpo(SuperCommand): """Group Policy Object (GPO) management.""" diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py index b5d195b0445..e28b117f73a 100644 --- a/python/samba/tests/gpo.py +++ b/python/samba/tests/gpo.py @@ -36,6 +36,7 @@ from samba.vgp_openssh_ext import vgp_openssh_ext from samba.vgp_startup_scripts_ext import vgp_startup_scripts_ext from samba.vgp_motd_ext import vgp_motd_ext from samba.vgp_issue_ext import vgp_issue_ext +from samba.vgp_access_ext import vgp_access_ext import logging from samba.credentials import Credentials from samba.gp_msgs_ext import gp_msgs_ext @@ -1366,3 +1367,133 @@ class GPOTests(tests.TestCase): # Unstage the manifest.xml file unstage_file(manifest) + + def test_vgp_access(self): + local_path = self.lp.cache_path('gpo_cache') + guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}' + allow = os.path.join(local_path, policies, guid, 'MACHINE', + 'VGP/VTLA/VAS/HOSTACCESSCONTROL/ALLOW/MANIFEST.XML') + deny = os.path.join(local_path, policies, guid, 'MACHINE', + 'VGP/VTLA/VAS/HOSTACCESSCONTROL/DENY/MANIFEST.XML') + logger = logging.getLogger('gpo_tests') + cache_dir = self.lp.get('cache directory') + store = GPOStorage(os.path.join(cache_dir, 'gpo.tdb')) + + machine_creds = Credentials() + machine_creds.guess(self.lp) + machine_creds.set_machine_account() + + # Initialize the group policy extension + ext = vgp_access_ext(logger, self.lp, machine_creds, store) + + ads = gpo.ADS_STRUCT(self.server, self.lp, machine_creds) + if ads.connect(): + gpos = ads.get_gpo_list(machine_creds.get_username()) + + # Stage the manifest.xml allow file + stage = etree.Element('vgppolicy') + policysetting = etree.SubElement(stage, 'policysetting') + version = etree.SubElement(policysetting, 'version') + version.text = '2' + apply_mode = etree.SubElement(policysetting, 'apply_mode') + apply_mode.text = 'merge' + data = etree.SubElement(policysetting, 'data') + # Add an allowed user + listelement = etree.SubElement(data, 'listelement') + otype = etree.SubElement(listelement, 'type') + otype.text = 'USER' + entry = etree.SubElement(listelement, 'entry') + entry.text = 'goodguy@%s' % realm + adobject = etree.SubElement(listelement, 'adobject') + name = etree.SubElement(adobject, 'name') + name.text = 'goodguy' + domain = etree.SubElement(adobject, 'domain') + domain.text = realm + otype = etree.SubElement(adobject, 'type') + otype.text = 'user' + # Add an allowed group + groupattr = etree.SubElement(data, 'groupattr') + groupattr.text = 'samAccountName' + listelement = etree.SubElement(data, 'listelement') + otype = etree.SubElement(listelement, 'type') + otype.text = 'GROUP' + entry = etree.SubElement(listelement, 'entry') + entry.text = '%s\\goodguys' % realm + dn = etree.SubElement(listelement, 'dn') + dn.text = 'CN=goodguys,CN=Users,%s' % base_dn + adobject = etree.SubElement(listelement, 'adobject') + name = etree.SubElement(adobject, 'name') + name.text = 'goodguys' + domain = etree.SubElement(adobject, 'domain') + domain.text = realm + otype = etree.SubElement(adobject, 'type') + otype.text = 'group' + ret = stage_file(allow, etree.tostring(stage)) + self.assertTrue(ret, 'Could not create the target %s' % allow) + + # Stage the manifest.xml deny file + stage = etree.Element('vgppolicy') + policysetting = etree.SubElement(stage, 'policysetting') + version = etree.SubElement(policysetting, 'version') + version.text = '2' + apply_mode = etree.SubElement(policysetting, 'apply_mode') + apply_mode.text = 'merge' + data = etree.SubElement(policysetting, 'data') + # Add a denied user + listelement = etree.SubElement(data, 'listelement') + otype = etree.SubElement(listelement, 'type') + otype.text = 'USER' + entry = etree.SubElement(listelement, 'entry') + entry.text = 'badguy@%s' % realm + adobject = etree.SubElement(listelement, 'adobject') + name = etree.SubElement(adobject, 'name') + name.text = 'badguy' + domain = etree.SubElement(adobject, 'domain') + domain.text = realm + otype = etree.SubElement(adobject, 'type') + otype.text = 'user' + # Add a denied group + groupattr = etree.SubElement(data, 'groupattr') + groupattr.text = 'samAccountName' + listelement = etree.SubElement(data, 'listelement') + otype = etree.SubElement(listelement, 'type') + otype.text = 'GROUP' + entry = etree.SubElement(listelement, 'entry') + entry.text = '%s\\badguys' % realm + dn = etree.SubElement(listelement, 'dn') + dn.text = 'CN=badguys,CN=Users,%s' % base_dn + adobject = etree.SubElement(listelement, 'adobject') + name = etree.SubElement(adobject, 'name') + name.text = 'badguys' + domain = etree.SubElement(adobject, 'domain') + domain.text = realm + otype = etree.SubElement(adobject, 'type') + otype.text = 'group' + ret = stage_file(deny, etree.tostring(stage)) + self.assertTrue(ret, 'Could not create the target %s' % deny) + + # Process all gpos, with temp output directory + with TemporaryDirectory() as dname: + ext.process_group_policy([], gpos, dname) + conf = os.listdir(dname) + self.assertEquals(len(conf), 1, 'The conf file was not created') -- Samba Shared Repository