]> git.ipfire.org Git - thirdparty/samba.git/commitdiff
samba-tool group: Add --special parameter to add predefined special group
authorJoseph Sutton <josephsutton@catalyst.net.nz>
Thu, 10 Feb 2022 04:14:56 +0000 (17:14 +1300)
committerStefan Metzmacher <metze@samba.org>
Fri, 18 Mar 2022 11:55:30 +0000 (11:55 +0000)
This allows default security groups that have been added since Windows
Server 2008 R2, such as Protected Users, to be created in pre-existing
domains. An error message is generated if a group already exists with
the same name, DN, or SID.

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
python/samba/netcmd/group.py
selftest/knownfail.d/samba-tool-group-special [deleted file]

index 3c8a9054339e0c32b07a0a3ce6515132dcc95b23..5666cde614d1c48deddf82f895357d4c6bdf910e 100644 (file)
 import samba.getopt as options
 from samba.netcmd import Command, SuperCommand, CommandError, Option
 import ldb
-from samba.ndr import ndr_unpack
+from samba.ndr import ndr_pack, ndr_unpack
 from samba.dcerpc import security
 
 from samba.auth import system_session
 from samba.samdb import SamDB
 from samba.dsdb import (
     ATYPE_SECURITY_GLOBAL_GROUP,
+    DS_GUID_USERS_CONTAINER,
     GTYPE_SECURITY_BUILTIN_LOCAL_GROUP,
     GTYPE_SECURITY_DOMAIN_LOCAL_GROUP,
     GTYPE_SECURITY_GLOBAL_GROUP,
@@ -33,11 +34,14 @@ from samba.dsdb import (
     GTYPE_DISTRIBUTION_DOMAIN_LOCAL_GROUP,
     GTYPE_DISTRIBUTION_GLOBAL_GROUP,
     GTYPE_DISTRIBUTION_UNIVERSAL_GROUP,
+    SYSTEM_FLAG_DISALLOW_DELETE,
+    SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE,
+    SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME,
     UF_ACCOUNTDISABLE,
 )
 from collections import defaultdict
 from subprocess import check_call, CalledProcessError
-from samba.common import get_bytes
+from samba.common import get_bytes, normalise_int32
 import os
 import tempfile
 from . import common
@@ -106,13 +110,15 @@ Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (b
         Option("--notes", help="Groups's notes", type=str),
         Option("--gid-number", help="Group's Unix/RFC2307 GID number", type=int),
         Option("--nis-domain", help="SFU30 NIS Domain", type=str),
+        Option("--special", help="Add a special predefined group", action="store_true", default=False),
     ]
 
     takes_args = ["groupname"]
 
     def run(self, groupname, credopts=None, sambaopts=None,
             versionopts=None, H=None, groupou=None, group_scope=None,
-            group_type=None, description=None, mail_address=None, notes=None, gid_number=None, nis_domain=None):
+            group_type=None, description=None, mail_address=None, notes=None, gid_number=None, nis_domain=None,
+            special=False):
 
         if (group_type or "Security") == "Security":
             gtype = security_group.get(group_scope, GTYPE_SECURITY_GLOBAL_GROUP)
@@ -128,6 +134,191 @@ Example3 adds a new RFC2307 enabled group for NIS domain samdom and GID 12345 (b
         try:
             samdb = SamDB(url=H, session_info=system_session(),
                           credentials=creds, lp=lp)
+        except Exception as e:
+            # FIXME: catch more specific exception
+            raise CommandError(f'Failed to add group "{groupname}"', e)
+
+        if special:
+            invalid_option = None
+            if group_scope is not None:
+                invalid_option = 'group-scope'
+            elif group_type is not None:
+                invalid_option = 'group-type'
+            elif description is not None:
+                invalid_option = 'description'
+            elif mail_address is not None:
+                invalid_option = 'mail-address'
+            elif notes is not None:
+                invalid_option = 'notes'
+            elif gid_number is not None:
+                invalid_option = 'gid-number'
+            elif nis_domain is not None:
+                invalid_option = 'nis-domain'
+
+            if invalid_option is not None:
+                raise CommandError(f'Superfluous option --{invalid_option} '
+                                   f'specified with --special')
+
+            if not samdb.am_pdc():
+                raise CommandError('Adding special groups is only permitted '
+                                   'against the PDC!')
+
+            special_groups = {
+                # On Windows, this group is added automatically when the PDC
+                # role is held by a DC running Windows Server 2012 R2 or later.
+                # https://docs.microsoft.com/en-us/windows-server/security/credentials-protection-and-management/protected-users-security-group#BKMK_Requirements
+                'Protected Users'.lower(): (
+                    'Protected Users',
+                    GTYPE_SECURITY_GLOBAL_GROUP,
+                    security.DOMAIN_RID_PROTECTED_USERS,
+                    'Members of this group are afforded additional '
+                    'protections against authentication security threats'),
+            }
+
+            special_group = special_groups.get(groupname.lower())
+            if special_group is None:
+                raise CommandError(f'Unknown special group "{groupname}".')
+
+            groupname, gtype, rid, description = special_group
+            group_type = normalise_int32(gtype)
+
+            group_dn = samdb.get_default_basedn()
+
+            if gtype == GTYPE_SECURITY_GLOBAL_GROUP:
+                object_sid = security.dom_sid(
+                    f'{samdb.get_domain_sid()}-{rid}')
+                system_flags = None
+
+                if not groupou:
+                    group_dn = samdb.get_wellknown_dn(group_dn,
+                                                      DS_GUID_USERS_CONTAINER)
+
+            elif gtype == GTYPE_SECURITY_BUILTIN_LOCAL_GROUP:
+                object_sid = security.dom_sid(f'S-1-5-32-{rid}')
+                system_flags = (SYSTEM_FLAG_DOMAIN_DISALLOW_MOVE |
+                                SYSTEM_FLAG_DOMAIN_DISALLOW_RENAME |
+                                SYSTEM_FLAG_DISALLOW_DELETE)
+
+                if not groupou and not group_dn.add_child('CN=Builtin'):
+                    raise RuntimeError('Error getting Builtin objects DN')
+            else:
+                raise RuntimeError(f'Unknown group type {gtype}')
+
+            if groupou and not group_dn.add_child(groupou):
+                raise CommandError(f'Invalid group OU "{groupou}"')
+
+            if not group_dn.add_child(f'CN={groupname}'):
+                raise CommandError(f'Invalid group name "{groupname}"')
+
+            msg = {
+                'dn': group_dn,
+                'sAMAccountName': groupname,
+                'objectClass': 'group',
+                'groupType': group_type,
+                'description': description,
+                'objectSid': ndr_pack(object_sid),
+                'isCriticalSystemObject': 'TRUE',
+            }
+
+            if system_flags is not None:
+                msg['systemFlags'] = system_flags
+
+            try:
+                samdb.add(msg, controls=['relax:0'])
+            except ldb.LdbError as e:
+                num, estr = e.args
+                if num == ldb.ERR_CONSTRAINT_VIOLATION:
+                    try:
+                        res = samdb.search(
+                            expression=f'(objectSid={object_sid})',
+                            attrs=['sAMAccountName'])
+                    except ldb.LdbError:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}"', e)
+
+                    if len(res) != 1:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}"', e)
+
+                    name = res[0].get('sAMAccountName', idx=0)
+                    if name:
+                        with_name = f' with name "{name}"'
+                    else:
+                        with_name = ''
+
+                    raise CommandError(
+                        f'Failed to add group "{groupname}" - Special group '
+                        f'already exists{with_name} at "{res[0].dn}".')
+
+                elif num == ldb.ERR_ENTRY_ALREADY_EXISTS:
+                    try:
+                        res = samdb.search(base=group_dn,
+                                           scope=ldb.SCOPE_BASE,
+                                           attrs=['sAMAccountName',
+                                                  'objectSid',
+                                                  'groupType'])
+                    except ldb.LdbError:
+                        try:
+                            res = samdb.search(
+                                expression=f'(sAMAccountName={groupname})',
+                                attrs=['sAMAccountName',
+                                       'objectSid',
+                                       'groupType'])
+                        except ldb.LdbError:
+                            raise CommandError(
+                                f'Failed to add group "{groupname}"', e)
+
+                    if len(res) != 1:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}"', e)
+
+                    got_name = res[0].get('sAMAccountName', idx=0)
+                    if got_name:
+                        named = f'named "{got_name}"'
+                    else:
+                        named = 'with no name'
+
+                    got_group_type = res[0].get('groupType',
+                                                idx=0).decode('utf-8')
+                    if group_type != got_group_type:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}" - An object '
+                            f'{named} at "{res[0].dn}" already exists, but it '
+                            f'is not a security group. Rename or remove this '
+                            f'existing object before attempting to add this '
+                            f'special group.')
+
+                    sid = res[0].get('objectSid', idx=0)
+                    if sid is None:
+                        raise CommandError(
+                            f'Failed to add group "{groupname}" - A security '
+                            f'group {named} at "{res[0].dn}" already exists, '
+                            f'but it lacks a SID. Rename or remove this '
+                            f'existing object before attempting to add this '
+                            f'special group.')
+                    else:
+                        sid = ndr_unpack(security.dom_sid, sid)
+                        if sid == object_sid:
+                            raise CommandError(
+                                f'Failed to add group "{groupname}" - The '
+                                f'security group {named} at "{res[0].dn}" '
+                                f'already exists.')
+                        else:
+                            raise CommandError(
+                                f'Failed to add group "{groupname}" - A '
+                                f'security group {named} at "{res[0].dn}" '
+                                f'already exists, but it has the wrong SID, '
+                                f'and will not function as expected. Rename '
+                                f'or remove this existing object before '
+                                f'attempting to add this special group.')
+                else:
+                    raise CommandError(f'Failed to add group "{groupname}"', e)
+            else:
+                self.outf.write(f'Added group {groupname}\n')
+
+            return
+
+        try:
             samdb.newgroup(groupname, groupou=groupou, grouptype=gtype,
                            description=description, mailaddress=mail_address, notes=notes,
                            gidnumber=gid_number, nisdomain=nis_domain)
diff --git a/selftest/knownfail.d/samba-tool-group-special b/selftest/knownfail.d/samba-tool-group-special
deleted file mode 100644 (file)
index 9ce7b0b..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-^samba4.blackbox.test_special_group.add_special_group.none
-^samba4.blackbox.test_special_group.add_duplicate_special_group.none