1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-23 17:34:34 +03:00

samba-tool group: Add --special parameter to add predefined special group

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>
This commit is contained in:
Joseph Sutton 2022-02-10 17:14:56 +13:00 committed by Stefan Metzmacher
parent 4f1b7684ed
commit 7b710a05de
2 changed files with 194 additions and 5 deletions

View File

@ -19,13 +19,14 @@
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)

View File

@ -1,2 +0,0 @@
^samba4.blackbox.test_special_group.add_special_group.none
^samba4.blackbox.test_special_group.add_duplicate_special_group.none