The branch, master has been updated via d0529682605 samba-tool: Add a gpo command for setting VGP OpenSSH Group Policy via be8f0d8ddbb samba-tool: Test gpo manage openssh set command via 3c47a81472b samba-tool: Add a gpo command for listing VGP OpenSSH Group Policy via 61394e5dd10 samba-tool: Test gpo manage openssh list command via ddf1cbd3452 gpo: Apply Group Policy OpenSSH settings from VGP via e9c1cc4e74b gpo: Test Group Policy OpenSSH for VGP from 6816135a2cd s3 lib system: Change signature of sys_proc_fd_path
https://git.samba.org/?p=samba.git;a=shortlog;h=master - Log ----------------------------------------------------------------- commit d052968260582290b3ced8f951d75603f642979d Author: David Mulder <dmul...@suse.com> Date: Wed Dec 23 14:52:35 2020 -0700 samba-tool: Add a gpo command for setting VGP OpenSSH 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): Tue Feb 9 21:24:14 UTC 2021 on sn-devel-184 commit be8f0d8ddbb9a145bd171618ef7183b9ed648203 Author: David Mulder <dmul...@suse.com> Date: Wed Dec 23 14:29:58 2020 -0700 samba-tool: Test gpo manage openssh set command Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit 3c47a81472b71642ef92522a27cbd2c14865bf43 Author: David Mulder <dmul...@suse.com> Date: Wed Dec 23 14:14:49 2020 -0700 samba-tool: Add a gpo command for listing VGP OpenSSH Group Policy Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit 61394e5dd10112190c4af875eed6fcc071ca3920 Author: David Mulder <dmul...@suse.com> Date: Wed Dec 23 12:25:11 2020 -0700 samba-tool: Test gpo manage openssh list command Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit ddf1cbd345237162ac19596ac1db787e0d77de65 Author: David Mulder <dmul...@suse.com> Date: Thu Nov 5 09:08:26 2020 -0700 gpo: Apply Group Policy OpenSSH settings from VGP Signed-off-by: David Mulder <dmul...@suse.com> Reviewed-by: Jeremy Allison <j...@samba.org> commit e9c1cc4e74be01c963a704ee9ed9466b1420da6d Author: David Mulder <dmul...@suse.com> Date: Wed Nov 4 10:24:26 2020 -0700 gpo: Test Group Policy OpenSSH 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 | 10 ++ python/samba/netcmd/gpo.py | 194 +++++++++++++++++++++++++++++++++++ python/samba/tests/gpo.py | 67 ++++++++++++ python/samba/tests/samba_tool/gpo.py | 90 ++++++++++++++++ python/samba/vgp_openssh_ext.py | 82 +++++++++++++++ source4/scripting/bin/samba-gpupdate | 2 + 6 files changed, 445 insertions(+) create mode 100644 python/samba/vgp_openssh_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 4de7602570b..ea1f5f8d0e7 100644 --- a/docs-xml/manpages/samba-tool.8.xml +++ b/docs-xml/manpages/samba-tool.8.xml @@ -914,6 +914,16 @@ <para>Remove VGP Files Group Policy from the sysvol</para> </refsect3> +<refsect3> + <title>gpo manage openssh list</title> + <para>List VGP OpenSSH Group Policy from the sysvol</para> +</refsect3> + +<refsect3> + <title>gpo manage openssh set</title> + <para>Sets a VGP OpenSSH Group Policy to 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 8decbe0a8af..a0629feb4cd 100644 --- a/python/samba/netcmd/gpo.py +++ b/python/samba/netcmd/gpo.py @@ -2763,6 +2763,199 @@ class cmd_files(SuperCommand): subcommands["add"] = cmd_add_files() subcommands["remove"] = cmd_remove_files() +class cmd_list_openssh(Command): + """List VGP OpenSSH Group Policy from the sysvol + +This command lists openssh options from the sysvol that will be applied to winbind clients. + +Example: +samba-tool gpo manage openssh 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\\SshCfg', + 'SshD\\manifest.xml']) + try: + xml_data = 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]: + return # The file doesn't exist, so there is nothing to list + if e.args[0] == 0xC0000022: # STATUS_ACCESS_DENIED + raise CommandError("The authenticated user does " + "not have sufficient privileges") + raise + + policy = xml_data.find('policysetting') + data = policy.find('data') + configfile = data.find('configfile') + for configsection in configfile.findall('configsection'): + if configsection.find('sectionname').text: + continue + for kv in configsection.findall('keyvaluepair'): + self.outf.write('%s %s\n' % (kv.find('key').text, + kv.find('value').text)) + +class cmd_set_openssh(Command): + """Sets a VGP OpenSSH Group Policy to the sysvol + +This command sets an openssh setting to the sysvol for applying to winbind +clients. Not providing a value will unset the policy. + +Example: +samba-tool gpo manage openssh set {31B2F340-016D-11D2-945F-00C04FB984F9} KerberosAuthentication Yes + """ + + synopsis = "%prog <gpo> <setting> [value] [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", "setting", "value?"] + + def run(self, gpo, setting, value=None, 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_dir = '\\'.join([realm.lower(), 'Policies', gpo, + 'MACHINE\\VGP\\VTLA\\SshCfg\\SshD']) + 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') + configfile = data.find('configfile') + 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 = 'Configuration File' + description = ET.SubElement(policysetting, 'description') + description.text = 'Represents Unix configuration file settings' + apply_mode = ET.SubElement(policysetting, 'apply_mode') + apply_mode.text = 'merge' + data = ET.SubElement(policysetting, 'data') + configfile = ET.SubElement(data, 'configfile') + configsection = ET.SubElement(configfile, 'configsection') + ET.SubElement(configsection, 'sectionname') + elif e.args[0] == 0xC0000022: # STATUS_ACCESS_DENIED + raise CommandError("The authenticated user does " + "not have sufficient privileges") + else: + raise + + if value is not None: + for configsection in configfile.findall('configsection'): + if configsection.find('sectionname').text: + continue # Ignore Quest SSH settings + settings = {} + for kv in configsection.findall('keyvaluepair'): + settings[kv.find('key')] = kv + if setting in settings.keys(): + settings[setting].text = value + else: + keyvaluepair = ET.SubElement(configsection, 'keyvaluepair') + key = ET.SubElement(keyvaluepair, 'key') + key.text = setting + dvalue = ET.SubElement(keyvaluepair, 'value') + dvalue.text = value + else: + for configsection in configfile.findall('configsection'): + if configsection.find('sectionname').text: + continue # Ignore Quest SSH settings + settings = {} + for kv in configsection.findall('keyvaluepair'): + settings[kv.find('key').text] = kv + if setting in settings.keys(): + configsection.remove(settings[setting]) + else: + raise CommandError("Cannot remove '%s' because it does " \ + "not exist" % setting) + + 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_openssh(SuperCommand): + """Manage OpenSSH Group Policy Objects""" + subcommands = {} + subcommands["list"] = cmd_list_openssh() + subcommands["set"] = cmd_set_openssh() + class cmd_manage(SuperCommand): """Manage Group Policy Objects""" subcommands = {} @@ -2771,6 +2964,7 @@ class cmd_manage(SuperCommand): subcommands["smb_conf"] = cmd_smb_conf() subcommands["symlink"] = cmd_symlink() subcommands["files"] = cmd_files() + subcommands["openssh"] = cmd_openssh() class cmd_gpo(SuperCommand): """Group Policy Object (GPO) management.""" diff --git a/python/samba/tests/gpo.py b/python/samba/tests/gpo.py index a3fdc920907..f2c857a8f26 100644 --- a/python/samba/tests/gpo.py +++ b/python/samba/tests/gpo.py @@ -32,6 +32,7 @@ from samba.vgp_symlink_ext import vgp_symlink_ext from samba.gpclass import gp_inf_ext from samba.gp_smb_conf_ext import gp_smb_conf_ext from samba.vgp_files_ext import vgp_files_ext +from samba.vgp_openssh_ext import vgp_openssh_ext import logging from samba.credentials import Credentials from samba.gp_msgs_ext import gp_msgs_ext @@ -1034,3 +1035,69 @@ class GPOTests(tests.TestCase): # Unstage the manifest and source files unstage_file(manifest) unstage_file(source_file) + + def test_vgp_openssh(self): + local_path = self.lp.cache_path('gpo_cache') + guid = '{31B2F340-016D-11D2-945F-00C04FB984F9}' + manifest = os.path.join(local_path, policies, guid, 'MACHINE', + 'VGP/VTLA/SSHCFG/SSHD/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_openssh_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 file with test data + stage = etree.Element('vgppolicy') + policysetting = etree.Element('policysetting') + stage.append(policysetting) + version = etree.Element('version') + version.text = '1' + policysetting.append(version) + data = etree.Element('data') + configfile = etree.Element('configfile') + configsection = etree.Element('configsection') + sectionname = etree.Element('sectionname') + configsection.append(sectionname) + kvpair = etree.Element('keyvaluepair') + key = etree.Element('key') + key.text = 'AddressFamily' + kvpair.append(key) + value = etree.Element('value') + value.text = 'inet6' + kvpair.append(value) + configsection.append(kvpair) + configfile.append(configsection) + data.append(configfile) + policysetting.append(data) + ret = stage_file(manifest, etree.tostring(stage)) + self.assertTrue(ret, 'Could not create the target %s' % manifest) + + # Process all gpos, with temp output directory + data = 'AddressFamily inet6' + 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') + gp_cfg = os.path.join(dname, conf[0]) + self.assertIn(data, open(gp_cfg, 'r').read(), + 'The sshd_config entry was not applied') + + # Remove policy + gp_db = store.get_gplog(machine_creds.get_username()) + del_gpos = get_deleted_gpos_list(gp_db, []) + ext.process_group_policy(del_gpos, [], dname) + self.assertFalse(os.path.exists(gp_cfg), + 'Unapply failed to cleanup config') + + # Unstage the Registry.pol file + unstage_file(manifest) diff --git a/python/samba/tests/samba_tool/gpo.py b/python/samba/tests/samba_tool/gpo.py index 05c7f30f0a8..d678a96352b 100644 --- a/python/samba/tests/samba_tool/gpo.py +++ b/python/samba/tests/samba_tool/gpo.py @@ -988,6 +988,96 @@ class GpoCmdTestCase(SambaToolCmdTest): os.environ["PASSWORD"])) self.assertNotIn(target_file, out, 'The test entry was still found!') + def test_vgp_openssh_list(self): + lp = LoadParm() + lp.load(os.environ['SERVERCONFFILE']) + local_path = lp.get('path', 'sysvol') + vgp_xml = os.path.join(local_path, lp.get('realm').lower(), 'Policies', + self.gpo_guid, 'Machine/VGP/VTLA/SshCfg', + 'SshD/manifest.xml') + + stage = etree.Element('vgppolicy') + policysetting = etree.SubElement(stage, 'policysetting') + pv = etree.SubElement(policysetting, 'version') + pv.text = '1' + name = etree.SubElement(policysetting, 'name') + name.text = 'Configuration File' + description = etree.SubElement(policysetting, 'description') + description.text = 'Represents Unix configuration file settings' + apply_mode = etree.SubElement(policysetting, 'apply_mode') + apply_mode.text = 'merge' + data = etree.SubElement(policysetting, 'data') + configfile = etree.SubElement(data, 'configfile') + etree.SubElement(configfile, 'filename') + configsection = etree.SubElement(configfile, 'configsection') + etree.SubElement(configsection, 'sectionname') + opt = etree.SubElement(configsection, 'keyvaluepair') + key = etree.SubElement(opt, 'key') + key.text = 'KerberosAuthentication' + value = etree.SubElement(opt, 'value') + value.text = 'Yes' + ret = stage_file(vgp_xml, etree.tostring(stage, 'utf-8')) + self.assertTrue(ret, 'Could not create the target %s' % vgp_xml) + + openssh = 'KerberosAuthentication Yes' + (result, out, err) = self.runsublevelcmd("gpo", ("manage", + "openssh", "list"), + self.gpo_guid, "-H", + "ldap://%s" % + os.environ["SERVER"], + "-U%s%%%s" % + (os.environ["USERNAME"], + os.environ["PASSWORD"])) + self.assertIn(openssh, out, 'The test entry was not found!') + + # Unstage the manifest.xml file + unstage_file(vgp_xml) + + def test_vgp_openssh_set(self): + (result, out, err) = self.runsublevelcmd("gpo", ("manage", + "openssh", "set"), + self.gpo_guid, + "KerberosAuthentication", + "Yes", "-H", + "ldap://%s" % + os.environ["SERVER"], + "-U%s%%%s" % + (os.environ["USERNAME"], + os.environ["PASSWORD"])) + self.assertCmdSuccess(result, out, err, 'OpenSSH set failed') + + openssh = 'KerberosAuthentication Yes' + (result, out, err) = self.runsublevelcmd("gpo", ("manage", + "openssh", "list"), + self.gpo_guid, "-H", + "ldap://%s" % + os.environ["SERVER"], + "-U%s%%%s" % + (os.environ["USERNAME"], + os.environ["PASSWORD"])) + self.assertIn(openssh, out, 'The test entry was not found!') + + (result, out, err) = self.runsublevelcmd("gpo", ("manage", + "openssh", "set"), + self.gpo_guid, + "KerberosAuthentication", "-H", + "ldap://%s" % + os.environ["SERVER"], + "-U%s%%%s" % + (os.environ["USERNAME"], + os.environ["PASSWORD"])) + self.assertCmdSuccess(result, out, err, 'OpenSSH unset failed') + + (result, out, err) = self.runsublevelcmd("gpo", ("manage", + "openssh", "list"), + self.gpo_guid, "-H", + "ldap://%s" % + os.environ["SERVER"], + "-U%s%%%s" % + (os.environ["USERNAME"], + os.environ["PASSWORD"])) + self.assertNotIn(openssh, out, 'The test entry was still found!') + def setUp(self): """set up a temporary GPO to work with""" super(GpoCmdTestCase, self).setUp() diff --git a/python/samba/vgp_openssh_ext.py b/python/samba/vgp_openssh_ext.py new file mode 100644 index 00000000000..488bfa728ae --- /dev/null +++ b/python/samba/vgp_openssh_ext.py @@ -0,0 +1,82 @@ +# vgp_openssh_ext samba group policy +# Copyright (C) David Mulder <dmul...@suse.com> 2020 +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +import os +from samba.gpclass import gp_xml_ext +from base64 import b64encode +from tempfile import NamedTemporaryFile +from samba.common import get_bytes, get_string + +intro = b''' +### autogenerated by samba +# +# This file is generated by the vgp_openssh_ext Group Policy +# Client Side Extension. To modify the contents of this file, +# modify the appropriate Group Policy objects which apply +# to this machine. DO NOT MODIFY THIS FILE DIRECTLY. +# + +''' + +class vgp_openssh_ext(gp_xml_ext): + def __str__(self): + return 'VGP/Unix Settings/OpenSSH' + + def process_group_policy(self, deleted_gpo_list, changed_gpo_list, + cfg_dir='/etc/ssh/sshd_config.d'): + for guid, settings in deleted_gpo_list: + self.gp_db.set_guid(guid) + if str(self) in settings: + for attribute, sshd_config in settings[str(self)].items(): + if os.path.exists(sshd_config): + os.unlink(sshd_config) + self.gp_db.delete(str(self), attribute) + self.gp_db.commit() + + for gpo in changed_gpo_list: + if gpo.file_sys_path: + self.gp_db.set_guid(gpo.name) + xml = 'MACHINE/VGP/VTLA/SshCfg/SshD/manifest.xml' + path = os.path.join(gpo.file_sys_path, xml) + xml_conf = self.parse(path) + if not xml_conf: + continue + policy = xml_conf.find('policysetting') + data = policy.find('data') + configfile = data.find('configfile') + for configsection in configfile.findall('configsection'): + if configsection.find('sectionname').text: + continue + settings = {} + for kv in configsection.findall('keyvaluepair'): + settings[kv.find('key')] = kv.find('value') + attribute = get_string(b64encode(get_bytes(gpo.name) + + get_bytes(cfg_dir))) + fname = self.gp_db.retrieve(str(self), attribute) + if fname and os.path.exists(fname): + f = open(fname, 'w') + else: + f = NamedTemporaryFile(prefix='gp_', + delete=False, + dir=cfg_dir) + f.write(intro) + for k, v in settings.items(): + f.write(b'%s %s\n' % \ + (get_bytes(k.text), get_bytes(v.text))) -- Samba Shared Repository