mirror of
https://github.com/samba-team/samba.git
synced 2025-08-03 04:22:09 +03:00
python/samba/netcmd/schema.py: add schema query and management.
Schema management in active directory is complex and dangerous. Having a tool that safely wraps administrative tasks as well as allowing query of the schema will make this complex topic more accessible to administrators. Signed-off-by: William Brown <william@blackhats.net.au> Reviewed-by: Andrew Bartlett <abartlet@samba.org> Reviewed-by: Garming Sam <garming@catalyst.net.nz>
This commit is contained in:
committed by
Andrew Bartlett
parent
289ae87c3b
commit
74d85d4cc2
@ -737,6 +737,26 @@
|
||||
<para>Preload one account for an RODC.</para>
|
||||
</refsect3>
|
||||
|
||||
<refsect2>
|
||||
<title>schema</title>
|
||||
<para>Manage and query schema.</para>
|
||||
</refsect2>
|
||||
|
||||
<refsect3>
|
||||
<title>schema attribute modify <replaceable>attribute</replaceable> [options]</title>
|
||||
<para>Modify the behaviour of an attribute in schema.</para>
|
||||
</refsect3>
|
||||
|
||||
<refsect3>
|
||||
<title>schema attribute show <replaceable>attribute</replaceable> [options]</title>
|
||||
<para>Display an attribute schema definition.</para>
|
||||
</refsect3>
|
||||
|
||||
<refsect3>
|
||||
<title>schema objectclass show <replaceable>objectclass</replaceable> [options]</title>
|
||||
<para>Display an objectclass schema definition.</para>
|
||||
</refsect3>
|
||||
|
||||
<refsect2>
|
||||
<title>sites</title>
|
||||
<para>Manage sites.</para>
|
||||
|
@ -36,14 +36,17 @@ bitFields["searchflags"] = {
|
||||
'fTUPLEINDEX': 26, # TP
|
||||
'fSUBTREEATTINDEX': 25, # ST
|
||||
'fCONFIDENTIAL': 24, # CF
|
||||
'fCONFIDENTAIL': 24, # typo
|
||||
'fNEVERVALUEAUDIT': 23, # NV
|
||||
'fRODCAttribute': 22, # RO
|
||||
|
||||
|
||||
# missing in ADTS but required by LDIF
|
||||
'fRODCFilteredAttribute': 22, # RO ?
|
||||
'fCONFIDENTAIL': 24, # typo
|
||||
'fRODCFILTEREDATTRIBUTE': 22 # case
|
||||
'fRODCFilteredAttribute': 22, # RO
|
||||
'fRODCFILTEREDATTRIBUTE': 22, # case
|
||||
'fEXTENDEDLINKTRACKING': 21, # XL
|
||||
'fBASEONLY': 20, # BO
|
||||
'fPARTITIONSECRET': 19, # SE
|
||||
}
|
||||
|
||||
# ADTS: 2.2.10
|
||||
|
@ -70,6 +70,7 @@ class cmd_sambatool(SuperCommand):
|
||||
subcommands["ldapcmp"] = None
|
||||
subcommands["ntacl"] = None
|
||||
subcommands["rodc"] = None
|
||||
subcommands["schema"] = None
|
||||
subcommands["sites"] = None
|
||||
subcommands["spn"] = None
|
||||
subcommands["testparm"] = None
|
||||
|
262
python/samba/netcmd/schema.py
Normal file
262
python/samba/netcmd/schema.py
Normal file
@ -0,0 +1,262 @@
|
||||
# Manipulate ACLs on directory objects
|
||||
#
|
||||
# Copyright (C) William Brown <william@blackhats.net.au> 2018
|
||||
#
|
||||
# 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 ldb
|
||||
import samba.getopt as options
|
||||
from samba.ms_schema import bitFields
|
||||
from samba.auth import system_session
|
||||
from samba.samdb import SamDB
|
||||
from samba.netcmd import (
|
||||
Command,
|
||||
CommandError,
|
||||
SuperCommand,
|
||||
Option
|
||||
)
|
||||
|
||||
class cmd_schema_attribute_modify(Command):
|
||||
"""Modify attribute settings in the schema partition.
|
||||
|
||||
This commands allows minor modifications to attributes in the schema. Active
|
||||
Directory does not allow many changes to schema, but important modifications
|
||||
are related to indexing. This command overwrites the value of searchflags,
|
||||
so be sure to view the current content before making changes.
|
||||
|
||||
Example1:
|
||||
samba-tool schema attribute modify uid \
|
||||
--searchflags="fATTINDEX,fPRESERVEONDELETE"
|
||||
|
||||
This alters the uid attribute to be indexed and to be preserved when
|
||||
converted to a tombstone.
|
||||
|
||||
Important search flag values are:
|
||||
|
||||
fATTINDEX: create an equality index for this attribute.
|
||||
fPDNTATTINDEX: create a container index for this attribute (ie OU).
|
||||
fANR: specify that this attribute is a member of the ambiguous name
|
||||
resolution set.
|
||||
fPRESERVEONDELETE: indicate that the value of this attribute should be
|
||||
preserved when the object is converted to a tombstone (deleted).
|
||||
fCOPY: hint to clients that this attribute should be copied.
|
||||
fTUPLEINDEX: create a tuple index for this attribute. This is used in
|
||||
substring queries.
|
||||
fSUBTREEATTINDEX: create a browsing index for this attribute. VLV searches
|
||||
require this.
|
||||
fCONFIDENTIAL: indicate that the attribute is confidental and requires
|
||||
special access checks.
|
||||
fNEVERVALUEAUDIT: indicate that changes to this value should NOT be audited.
|
||||
fRODCFILTEREDATTRIBUTE: indicate that this value should not be replicated to
|
||||
RODCs.
|
||||
fEXTENDEDLINKTRACKING: indicate to the DC to perform extra link tracking.
|
||||
fBASEONLY: indicate that this attribute should only be displayed when the
|
||||
search scope of the query is SCOPE_BASE or a single object result.
|
||||
fPARTITIONSECRET: indicate that this attribute is a partition secret and
|
||||
requires special access checks.
|
||||
|
||||
The authoritative source of this information is the MS-ADTS.
|
||||
"""
|
||||
synopsis = "%prog attribute [options]"
|
||||
|
||||
takes_optiongroups = {
|
||||
"sambaopts": options.SambaOptions,
|
||||
"versionopts": options.VersionOptions,
|
||||
"credopts": options.CredentialsOptions,
|
||||
}
|
||||
|
||||
takes_options = [
|
||||
Option("--searchflags", help="Search Flags for the attribute", type=str),
|
||||
Option("-H", "--URL", help="LDB URL for database or target server",
|
||||
type=str, metavar="URL", dest="H"),
|
||||
]
|
||||
|
||||
takes_args = ["attribute"]
|
||||
|
||||
def run(self, attribute, H=None, credopts=None, sambaopts=None,
|
||||
versionopts=None, searchflags=None):
|
||||
|
||||
if searchflags is None:
|
||||
raise CommandError('A value to modify must be provided.')
|
||||
|
||||
# Parse the search flags to a set of bits to modify.
|
||||
|
||||
searchflags_int = None
|
||||
if searchflags is not None:
|
||||
searchflags_int = 0
|
||||
flags = searchflags.split(',')
|
||||
# We have to normalise all the values. To achieve this predictably
|
||||
# we title case (Fattrindex), then swapcase (fATTINDEX)
|
||||
flags = [ x.capitalize().swapcase() for x in flags ]
|
||||
for flag in flags:
|
||||
if flag not in bitFields['searchflags'].keys():
|
||||
raise CommandError("Unknown flag '%s', please see --help" % flag)
|
||||
bit_loc = 31 - bitFields['searchflags'][flag]
|
||||
# Now apply the bit.
|
||||
searchflags_int = searchflags_int | (1 << bit_loc)
|
||||
|
||||
lp = sambaopts.get_loadparm()
|
||||
creds = credopts.get_credentials(lp)
|
||||
|
||||
samdb = SamDB(url=H, session_info=system_session(),
|
||||
credentials=creds, lp=lp)
|
||||
|
||||
schema_dn = samdb.schema_dn()
|
||||
# For now we make assumptions about the CN
|
||||
attr_dn = 'cn=%s,%s' % (attribute, schema_dn)
|
||||
|
||||
m = ldb.Message()
|
||||
m.dn = ldb.Dn(samdb, attr_dn)
|
||||
|
||||
if searchflags_int is not None:
|
||||
m['searchFlags'] = ldb.MessageElement(
|
||||
str(searchflags_int), ldb.FLAG_MOD_REPLACE, 'searchFlags')
|
||||
|
||||
samdb.modify(m)
|
||||
print("modified %s" % attr_dn)
|
||||
|
||||
class cmd_schema_attribute_show(Command):
|
||||
"""Show details about an attribute from the schema.
|
||||
|
||||
Schema attribute definitions define and control the behaviour of directory
|
||||
attributes on objects. This displays the details of a single attribute.
|
||||
"""
|
||||
synopsis = "%prog attribute [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 = ["attribute"]
|
||||
|
||||
def run(self, attribute, H=None, credopts=None, sambaopts=None, versionopts=None):
|
||||
lp = sambaopts.get_loadparm()
|
||||
creds = credopts.get_credentials(lp)
|
||||
|
||||
samdb = SamDB(url=H, session_info=system_session(),
|
||||
credentials=creds, lp=lp)
|
||||
|
||||
schema_dn = samdb.schema_dn()
|
||||
|
||||
filt = '(&(objectClass=attributeSchema)(|(lDAPDisplayName={0})(cn={0})(name={0})))'.format(attribute)
|
||||
|
||||
res = samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE,
|
||||
expression=filt)
|
||||
|
||||
if len(res) == 0:
|
||||
raise CommandError('No schema objects matched "%s"' % attribute)
|
||||
if len(res) > 1:
|
||||
raise CommandError('Multiple schema objects matched "%s": this is a serious issue you should report!' % attribute)
|
||||
|
||||
# Get the content of searchFlags (if any) and manipulate them to
|
||||
# show our friendly names.
|
||||
|
||||
# WARNING: If you are reading this in the future trying to change an
|
||||
# ldb message dynamically, and wondering why you get an operations
|
||||
# error, it's related to talloc references.
|
||||
#
|
||||
# When you create *any* python reference, IE:
|
||||
# flags = res[0]['attr']
|
||||
# this creates a talloc_reference that may live forever due to pythons
|
||||
# memory management model. However, when you create this reference it
|
||||
# blocks talloc_realloc from functions in msg.add(element).
|
||||
#
|
||||
# As a result, you MUST avoid ALL new variable references UNTIL you have
|
||||
# modified the message as required, even if it makes your code more
|
||||
# verbose.
|
||||
|
||||
if 'searchFlags' in res[0].keys():
|
||||
flags_i = None
|
||||
try:
|
||||
# See above
|
||||
flags_i = int(str(res[0]['searchFlags']))
|
||||
except ValueError:
|
||||
raise CommandError('Invalid schemaFlags value "%s": this is a serious issue you should report!' % res[0]['searchFlags'])
|
||||
# Work out what keys we have.
|
||||
out = []
|
||||
for flag in bitFields['searchflags'].keys():
|
||||
if flags_i & (1 << (31 - bitFields['searchflags'][flag])) != 0:
|
||||
out.append(flag)
|
||||
if len(out) > 0:
|
||||
res[0].add(ldb.MessageElement(out, ldb.FLAG_MOD_ADD, 'searchFlagsDecoded'))
|
||||
|
||||
user_ldif = samdb.write_ldif(res[0], ldb.CHANGETYPE_NONE)
|
||||
self.outf.write(user_ldif)
|
||||
|
||||
class cmd_schema_objectclass_show(Command):
|
||||
"""Show details about an objectClass from the schema.
|
||||
|
||||
Schema objectClass definitions define and control the behaviour of directory
|
||||
objects including what attributes they may contain. This displays the
|
||||
details of an objectClass.
|
||||
"""
|
||||
synopsis = "%prog objectclass [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 = ["objectclass"]
|
||||
|
||||
def run(self, objectclass, H=None, credopts=None, sambaopts=None, versionopts=None):
|
||||
lp = sambaopts.get_loadparm()
|
||||
creds = credopts.get_credentials(lp)
|
||||
|
||||
samdb = SamDB(url=H, session_info=system_session(),
|
||||
credentials=creds, lp=lp)
|
||||
|
||||
schema_dn = samdb.schema_dn()
|
||||
|
||||
filt = '(&(objectClass=classSchema)' \
|
||||
'(|(lDAPDisplayName={0})(cn={0})(name={0})))'.format(objectclass)
|
||||
|
||||
res = samdb.search(base=schema_dn, scope=ldb.SCOPE_SUBTREE,
|
||||
expression=filt)
|
||||
|
||||
for msg in res:
|
||||
user_ldif = samdb.write_ldif(msg, ldb.CHANGETYPE_NONE)
|
||||
self.outf.write(user_ldif)
|
||||
|
||||
class cmd_schema_attribute(SuperCommand):
|
||||
"""Query and manage attributes in the schema partition."""
|
||||
subcommands = {}
|
||||
subcommands["modify"] = cmd_schema_attribute_modify()
|
||||
subcommands["show"] = cmd_schema_attribute_show()
|
||||
|
||||
class cmd_schema_objectclass(SuperCommand):
|
||||
"""Query and manage objectclasses in the schema partition."""
|
||||
subcommands = {}
|
||||
subcommands["show"] = cmd_schema_objectclass_show()
|
||||
|
||||
class cmd_schema(SuperCommand):
|
||||
"""Schema querying and management."""
|
||||
|
||||
subcommands = {}
|
||||
subcommands["attribute"] = cmd_schema_attribute()
|
||||
subcommands["objectclass"] = cmd_schema_objectclass()
|
@ -90,6 +90,10 @@ class SamDB(samba.Ldb):
|
||||
'''return the domain DN'''
|
||||
return str(self.get_default_basedn())
|
||||
|
||||
def schema_dn(self):
|
||||
'''return the schema partition dn'''
|
||||
return str(self.get_schema_basedn())
|
||||
|
||||
def disable_account(self, search_filter):
|
||||
"""Disables an account
|
||||
|
||||
|
88
python/samba/tests/samba_tool/schema.py
Normal file
88
python/samba/tests/samba_tool/schema.py
Normal file
@ -0,0 +1,88 @@
|
||||
# Unix SMB/CIFS implementation.
|
||||
# Copyright (C) William Brown <william@blackhats.net.au> 2018
|
||||
#
|
||||
# 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
|
||||
import ldb
|
||||
from samba.tests.samba_tool.base import SambaToolCmdTest
|
||||
|
||||
class SchemaCmdTestCase(SambaToolCmdTest):
|
||||
"""Tests for samba-tool dsacl subcommands"""
|
||||
samdb = None
|
||||
|
||||
def setUp(self):
|
||||
super(SchemaCmdTestCase, self).setUp()
|
||||
self.samdb = self.getSamDB("-H", "ldap://%s" % os.environ["DC_SERVER"],
|
||||
"-U%s%%%s" % (os.environ["DC_USERNAME"], os.environ["DC_PASSWORD"]))
|
||||
|
||||
def tearDown(self):
|
||||
super(SchemaCmdTestCase, self).tearDown()
|
||||
|
||||
def test_display_attribute(self):
|
||||
"""Tests that we can display schema attributes"""
|
||||
(result, out, err) = self.runsubcmd("schema", "attribute",
|
||||
"show", "uid",
|
||||
"-H", "ldap://%s" % os.environ["DC_SERVER"],
|
||||
"-U%s%%%s" % (os.environ["DC_USERNAME"],
|
||||
os.environ["DC_PASSWORD"]))
|
||||
|
||||
self.assertCmdSuccess(result, out, err)
|
||||
|
||||
def test_modify_attribute_searchflags(self):
|
||||
"""Tests that we can modify searchFlags of an attribute"""
|
||||
(result, out, err) = self.runsubcmd("schema", "attribute",
|
||||
"modify", "uid", "--searchflags=9",
|
||||
"-H", "ldap://%s" % os.environ["DC_SERVER"],
|
||||
"-U%s%%%s" % (os.environ["DC_USERNAME"],
|
||||
os.environ["DC_PASSWORD"]))
|
||||
|
||||
self.assertCmdFail(result, 'Unknown flag 9, please see --help')
|
||||
|
||||
(result, out, err) = self.runsubcmd("schema", "attribute",
|
||||
"modify", "uid", "--searchflags=fATTINDEX",
|
||||
"-H", "ldap://%s" % os.environ["DC_SERVER"],
|
||||
"-U%s%%%s" % (os.environ["DC_USERNAME"],
|
||||
os.environ["DC_PASSWORD"]))
|
||||
|
||||
self.assertCmdSuccess(result, out, err)
|
||||
|
||||
(result, out, err) = self.runsubcmd("schema", "attribute",
|
||||
"modify", "uid",
|
||||
"--searchflags=fATTINDEX,fSUBTREEATTINDEX",
|
||||
"-H", "ldap://%s" % os.environ["DC_SERVER"],
|
||||
"-U%s%%%s" % (os.environ["DC_USERNAME"],
|
||||
os.environ["DC_PASSWORD"]))
|
||||
|
||||
self.assertCmdSuccess(result, out, err)
|
||||
|
||||
(result, out, err) = self.runsubcmd("schema", "attribute",
|
||||
"modify", "uid",
|
||||
"--searchflags=fAtTiNdEx,fPRESERVEONDELETE",
|
||||
"-H", "ldap://%s" % os.environ["DC_SERVER"],
|
||||
"-U%s%%%s" % (os.environ["DC_USERNAME"],
|
||||
os.environ["DC_PASSWORD"]))
|
||||
|
||||
self.assertCmdSuccess(result, out, err)
|
||||
|
||||
def test_display_objectclass(self):
|
||||
"""Tests that we can display schema objectclasses"""
|
||||
(result, out, err) = self.runsubcmd("schema", "objectclass",
|
||||
"show", "person",
|
||||
"-H", "ldap://%s" % os.environ["DC_SERVER"],
|
||||
"-U%s%%%s" % (os.environ["DC_USERNAME"],
|
||||
os.environ["DC_PASSWORD"]))
|
||||
|
||||
self.assertCmdSuccess(result, out, err)
|
@ -620,6 +620,7 @@ planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.group")
|
||||
planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.ou")
|
||||
planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.computer")
|
||||
planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.forest")
|
||||
planpythontestsuite("ad_dc_ntvfs:local", "samba.tests.samba_tool.schema")
|
||||
planpythontestsuite("ad_dc:local", "samba.tests.samba_tool.ntacl")
|
||||
planpythontestsuite("none", "samba.tests.samba_tool.provision_password_check")
|
||||
planpythontestsuite("none", "samba.tests.samba_tool.help")
|
||||
|
Reference in New Issue
Block a user