[Из песочницы] Samba4 — использование Python Scripting Interface

Samba4 имеет встроенный интерфейс на Python. Многие утилиты (samba-tool, например) полностью реализованы на Python с применением этого интерфейса.Все, что делалось из LDAP-интерфейса, можно сделать на Samba 4 Python Scripting. Преимущества — файловый доступ, значит высокая скорость, некоторые фичи, которых нет в LDAP. Например, можно взять хэш паролей пользователей из одной базы и перекинуть в другую. Да и самих пользователей с их SID-ами, паролями и всем прочим перекинуть в другой домен (без заморочек с SID-history).Документации маловато, но есть примеры в каталоге /python/samba, если есть исходники, иначе где-то в /usr/lib/python2.7/dist-packages/samba.

Наибольший интерес представляет файл samdb.py — реализация большинства операций в AD.

Пусть мы имеем установленную Samba4 в конфигурации AD domain controller. Попробуем подключиться к базе AD из Python-программы. Для начала импортируем необходимые библиотеки:

#!/usr/bin/env python # -*- coding: utf-8 -*- import ldb from samba.samdb import SamDB from samba.auth import system_session from samba.ndr import ndr_pack, ndr_unpack from samba.dcerpc import security import samba.param import base64 import binascii Подключение к основной базе /sam.ldb: lp = samba.param.LoadParm () lp.load (samba.param.default_path ()) #или lp.load (»/etc/samba/smb.conf») sam = SamDB (lp=lp, session_info=system_session ()) (Возможно подключение и при нестандартном расположении файлов и каталогов инсталляции Samba4 и даже к отдельно стоящей временной базе. Об этом ниже.)Теперь объект sam позволяет осуществлять поиск и модификацию базы AD в полном соответствии с синтаксисом LDAP.Например, поиск по базе (base — узел дерева LDAP типа «CN=Users, DC=myDom, DC=lan», expression — необязательное условие отбора, attrs — список желаемых атрибутов):

res = sam.search (base=base, expression=expression, attrs=[*]) Пусть пользователи лежат в ОУ: base = «OU=myUsers, DC=myDom, DC=lan» # можно не заморачиваться с ОУ и тогда base = «CN=Users, DC=myDom, DC=lan» Создадим пользователя «tst» c паролем «secret». Класс SamDB имеет готовый метод — newuser (), но можно попробовать и так: newUsr = «tst» usrPass = «secret»

ld = {'dn': 'CN=%s,%s' % (newUsr, base), «sAMAccountName»: newUsr, «userPrincipalName»:»%s@%s» % (newUsr, «myDom.lan»), «objectClass»: «user», «displayName»: newUsr, «description»: newUsr, «homeDirectory»: r»\\%s\users\%s» % («myHost», newUsr), 'scriptPath': «loginScr.cmd», } sam.transaction_start () try: sam.add (ld) sam.setpassword (»(samAccountName=%s)» % ldb.binary_encode (newUsr), usrPass, False) except: sam.transaction_cancel () print '!!! error' else: sam.transaction_commit () Как видим, SamDB поддерживает транзакции.Всю базу AD, если она не очень большая, можем посмотреть (и отредактировать) командой:

:~# ldbedit -e nano -H /var/lib/samba/private/sam.ldb Но лучше ограничивать выборку с помощью опции -s или -b (база), например, -b 'CN=RID Manager$, CN=System, DC=myDom, DC=com'.Перенос хешей паролей можно сделать по такой схеме:

Пусть у нас есть старая база AD — тоже на Samba4. Можно получить реплику базы из Win AD, подключив новую инсталляцию Samba4 в качестве дополнительного AD DC — хорошо документированная и простая процедура — см. здесь.

Скопируем и подключимся к ней — назовем соединение sam0. Подключение с нестандартными путями (пусть скопирована в /tmp/priv и там же его smb.conf):

lp0 = samba.param.LoadParm () lp0.load ('/tmp/priv/smb.conf') lp0.set ('private directory','/tmp/priv') sam0 = SamDB (lp=lp0, session_info=system_session ()) Чтобы получить весь список пользователей, да еще с паролями, сделаем такой запрос: res = sam0.search (base=«DC=oldDom, DC=myDom, DC=ru», expression=»(&(objectCategory=person)(objectClass=user))», attrs=['*','unicodePwd']) Будем перебирать базу пользователей и добавлять их в новую базу. Схематически это выгляди так: for r in res: dn = str (r.dn)# это старый DN пользователя, его нужно поменять чтобы соотв. новому домену! ------- sd = ndr_unpack (security.dom_sid, r['objectSid'][0])# это SID пользователя, можем оставить его прежним (dom_sid, rid) = sd.split ()# так SID разделяется на SID домена и RID ------- #… после необходимых преобразований добавляем пользователя в новую базу — sam.add (ld). (Работающий пример — ниже) #теперь трюк для переноса пароля: setpw = »« dn: %s changetype: modify replace: unicodePwd unicodePwd: %s »« % (dn, base64.b64encode (str (r['unicodePwd']))) sam.transaction_start () try: sam.modify_ldif (setpw,[«local_oid:1.3.6.1.4.1.7165.4.3.12:0»]) except: sam.transaction_cancel () print ('!!! ERROR SET PASSWORD USER: %s' % r['sAMAccountName']) else: sam.transaction_commit () Теперь реальный пример переноса пользователей из домена под Win 2003 в Samba4.В старом домене накопились проблемы (начиная даже с неправильного имени домена). Нормальная репликация с DC на Samba4 (в обратную сторону — Samba4 DC → W2003 DC) никак не завелась, вероятно из-за внутридоменных проблем.Задача отягощалась наличием файлового сервера на Samba3, поэтому надо было сохранить мапинг sAMAccountName <-> (UID, GID), уже существующий в Samba3 (обычно /var/lib/samba/winbindd_idmap.tdb). Собственно задача была похожа на описанную здесь.

Все эксперименты и конечный вариант делались на серверах Ubuntu 14.04, запущенных в контейнерах OpenVZ (CentOS 6)Установка, настройка Samba4 описана много раз. Например, уже упомянуто, здесь. Для нормального отображения Unix ID в схеме с rfc2307 использовался sssd. Кстати, сборку Samba4 от sernet, которую многие рекомендуют, лучше не использовать — ее трудно подружить с пакетом sssd.

Чтобы сохранить пароли и SID пользователей, надо иметь старую базу AD уже в виде private directory Samba4, как уже говорилось выше. Пропуская подробности (см. здесь), — «samba-tool domain join samdom.example.com DC -Uadministrator --realm=samdom.example.com» — можно на этом и остановиться, не запуская сервис samba, поскольку необходимая база уже создана. Если же надо будет далее актуализировать базу, то без запуска сервиса samba не обойтись.

Воздействие на существующий Win AD домен минимально (создается еще один контроллер, почти неработающий, поэтому в логах будет много ошибок NTDS Replication), после создания автономой базы AD можно без риска поупражняться в виртуальных средах. Если MS Win домен должен еще какое-то время нормально работать, лучше эту временную Samba4 убить и вычистить информацию об этом DC из работающих DC.

Полученную private directory (обычно /var/lib/samba/private или /usr/local/samba/private) надо скопировать куда-нибудь на будущий Samba4 и туда же скопировать smb.conf из /etc/samba. Теперь все данные о старом домене хранятся в одном месте и к тому же доступны в локальной ФС.

Если есть еще файл-сервер на Samba3, то где-нибудь рядом надо также положить и каталог /var/lib/samba от Samba3 (там нужны 2 файла — winbindd_idmap.tdb и group_mapping.tdb), если мы хотим сохранить сложившийся в Samba3 idmap.

Исходные параметры оформляем в виде файла conf.py:

#!/usr/bin/env python # -*- coding: utf-8 -*-

smb_conf = '/etc/samba/smb.conf' smb_priv = '/var/lib/samba/private' dom0 = 'olddom.mydom.ru' # название старого домена dom1 = 'newdom.lan' # название нового домена. (!)точек не больше, чем в старом — для определенности и простоты преобразования host = 'newdc' # сетевое имя хоста maildom = 'mydom.ru' #почтовый домен. необязательно #homeDirectory, homeDrive = r'\\newdc\Users','Z:' # необязательно smb_priv0 = '/var/lib/samba/private-0' #private directory старого домена на samba4 (и там же его smb.conf!) smb3db = '/var/lib/samba/samba3' #если был файл-сервер на самба3 (обычно /var/lib/samba (скопировать)), иначе None start_unix_id = 50000 # начало нумерации GID и UID для новых пользователей.

############ ниже лучше не трогать d0, d1 = dom0.split ('.'), dom1.split ('.') base0 = ','.join (['DC='+x for x in d0]) base, dom, realm = ','.join (['DC='+x for x in d1]), d1[0].upper (), '.'.join (d1).upper () Соберем основные функции переноса в один файлlib1.py #!/usr/bin/env python # -*- coding: utf-8 -*-

import sys import string import ldb from struct import unpack #from samba.idmap import IDmapDB from samba.samdb import SamDB from samba.auth import system_session from samba.ndr import ndr_unpack from samba.dcerpc import security import samba.param import base64 from conf import *

def get_1out (cmd): #возвращает 1 строку вывода внешней команды import subprocess return subprocess.Popen (cmd, stdout=subprocess.PIPE).stdout.read ().splitlines ()[0]

if not 'host' in globals (): host = get_1out ('hostname').upper () if not 'smb3db' in globals (): smb3db = None

my_log = lambda *x: None chgDc = lambda x: x

def set_my_log (): #вывод сообщений в лог-файл. Имя файла соотв. времени создания from datetime import datetime global my_log f_log = open (datetime.strftime (datetime.now (), «log_%y-%m-%d_%H:%M:%S.txt»),'w') def my_log (*x): try: xx = ' '.join (x) except: xx = ' '.join (map (lambda a: str (a), x)) f_log.write (xx+'\n') print xx

def mk_chg (d0=d0, d1=d1): #формирователь смены имен. d0, d1 списки элементов старого и нового домена. !!!: len (d0) >= len (d1) import re global chgDc if d0 == d1: return dif = len (d0)-len (d1)+1 #если в новом домене меньше элементов имени, ddx = [d0[0],] + d0[dif:] #получаем список с равным числом элементов, иначе ddx == d0 #1 удалить лишнее, 2 заменить маленькие буквы, 3 .большие. : myRe = [('', re.compile (r'\b (DC=)?%s\b[,.]?' % x, re.I)) for x in d0[1: dif]] +\ [(b, re.compile (r'\b%s\b' % a)) for (a, b) in zip (ddx, d1)] +\ [(b.upper (), re.compile (r'\b%s\b' % a.upper (), re.I)) for (a, b) in zip (ddx, d1)] def chg (s): #заменяет старое на новое d0 → d1 for r in myRe: s = r[1].sub (r[0], s) return s chgDc = chg return chg

def mk_sam0(smb_priv0=smb_priv0): #подключение к базе старого домена. smb_priv0 — private directory старого домена (и там же его smb.conf!) import os global sam0 lp0 = samba.param.LoadParm () lp0.load (smb_priv0+'/smb.conf') # smb.conf надо обязательно скопировать в private directory!!! lp0.set ('private directory', smb_priv0) sam0 = SamDB (lp=lp0, session_info=system_session ()) os.unsetenv ('SMB_CONF_PATH') return sam0

def mk_sam (host=host): #подключение к базе нового домена и нахождение нужных параметров global sam, ridSet, ridMan, sfu, idmap, minRid, nis lp = samba.param.LoadParm () lp.load (smb_conf) lp.set ('private directory', smb_priv) sam = SamDB (lp=lp, session_info=system_session ()) # idmap = IDmapDB (lp=lp) sfu = «CN=%s, CN=ypservers, CN=ypServ30, CN=RpcServices, CN=System,%s» % (dom, base) # dn для sfu (msSFU30OrderNumber…) ridSet = «CN=RID Set, CN=%s, OU=Domain Controllers,%s» % (host, base) # dn для rIDNextRID ridMan = «CN=RID Manager$, CN=System,»+base # dn для RID Manager if not 'minRid' in globals (): minRid = int (sam.search (base = ridSet, attrs=[«rIDNextRID»])[0][«rIDNextRID»][0]) my_log ('\tminRid=%s' % str (minRid)) if not 'nis' in globals (): nis = str (sam.search (base=sfu, attrs=['msSFU30Domains'])[0]['msSFU30Domains'][0]) sam.nis = nis return sam def get_map0(i=None, path=smb3db, maps = {}): # соответствие sAMAccountName <-> UID из файлов idmap smb3 — словарь maps if not maps: if path!= None: from samba.samba3 import DbDatabase mapdb = DbDatabase (path+'/group_mapping') for x in mapdb.db.iterkeys (): if x.startswith ('UNIXGROUP') : y = mapdb.db.get (x) maps[y[8:-2]] = ('GID', unpack (' 0 and 'sAMAccountName' in res[0]: maps[str (res[0][«sAMAccountName»][0])] = y.rstrip (»\x00»).split (» ») mapdb.close () else: # если не задана база idmap, все равно надо задать или определить ID 'Domain Users' my_log ('?! Not MAP0') maps['Domain Users'] = ['GID', start_unix_id] if 'sam' in globals (): res = sam.search (base=base, expression=»(sAMAccountName=Domain Users)», attrs=['gidNumber']) if len (res) > 0 and 'gidNumber' in res[0]: maps['Domain Users'][1] = int (res[0]['gidNumber'][0]) maps['_users'] = maps['Domain Users'][1] # спец. группа для всех my_log ('Set Domain Users = %d' % maps['_users']) maps['Administrator'] = ('UID',0) maps['Administrators'] = ('GID',0) if i == None: return maps return maps[i] if i in maps else False def mk_fill_matrix (m, r): # helper для заполнения словаря m в cp_usr () и cp_grp () def rp (k, chg=0): # если chg!=0 — используется преобразование имен! if k in r: m[k] = str (r[k][0]) if chg==0 else chgDc (str (r[k][0])) return rp

def mk_fill_ldb_msg (dn): # helper для создания и заполнения объекта ldb.Message m2 m2 = ldb.Message () m2.dn = ldb.Dn (sam, str (dn)) def rp (fld=None, val='', flg=ldb.FLAG_MOD_REPLACE): if fld: m2[fld] = ldb.MessageElement (str (val), flg, fld) return m2 return rp

def usn_sort (res): # сортировка объектов выборки res в порядке создания — важно для вложенных объектов (ou, grp) x = [r for r in res] x.sort (key = lambda r: int (r[«uSNCreated»][0])) return x def rid_sort (res): # сортировка объектов выборки res в порядке RID x = [r for r in res] x.sort (key = lambda r: int (unpack ('

def set_grp_gid (r, gid): #установить Unix GID для группы rp = mk_fill_ldb_msg (r.dn) rp («msSFU30NisDomain», nis) rp («msSFU30Name», r['sAMAccountName'][0]) rp («gidNumber», gid) sam.modify (rp ())

def set_usr_gid_uid (r, uid): #установить Unix GID, UID для пользователя rp = mk_fill_ldb_msg (r.dn) rp («msSFU30NisDomain», nis) rp («uid», r['sAMAccountName'][0]) rp («uidNumber», uid) rp («gidNumber», get_map0('_users')) # rp ('objectClass','posixAccount', ldb.FLAG_MOD_ADD) sam.modify (rp ())

def map_grp (): # ставим соотв GID из IdMap базы Samba 3 res = sam.search (base=base, expression=»(objectClass=group)») my_log (»\tmap_grp ALL GRP COUNT: %s» % len (res)) sam.transaction_start () try: for r in res: x = get_map0(str (r['sAMAccountName'][0])) if x: set_grp_gid (r, x[1]) except: sam.transaction_cancel () my_log ('!!! ERROR MAP GRP %s' % r['sAMAccountName']) else: sam.transaction_commit ()

def map_usr (): # ставим соотв UID из IdMap базы Samba 3 res = sam.search (base=base, expression=»(&(objectCategory=person)(objectClass=user))», attrs=[«sAMAccountName»]) my_log (»\tmap_usr ALL USR COUNT: %s» % len (res)) sam.transaction_start () try: for r in res: x = get_map0(str (r['sAMAccountName'][0])) if x: set_usr_gid_uid (r, x[1]) except: sam.transaction_cancel () my_log ('!!! ERROR SET SFU30 ATTR. USER %s!!!' % x[1]) else: sam.transaction_commit ()

def cp_ou (): # перенос organizationalUnit ous = [str (r.dn) for r in sam.search (base=base, expression=»(objectClass=organizationalUnit)», attrs=[])] res = sam0.search (base=base0, expression=»(objectClass=organizationalUnit)») my_log (»\tOU COUNT: %s» % len (res)) for r in usn_sort (res): dn = chgDc (str (r.dn)) if not dn in ous: m = {«dn»: dn, «objectClass»: «organizationalUnit», «name»: str (r[«name»][0])} try: sam.add (m) except: my_log (»!!! Error Add OU: %s» % dn)

def cp_usr (): # перенос пользователей users = [str (r.dn) for r in sam.search (base=base, expression=»(&(objectCategory=person)(objectClass=user))», attrs=[])] res = sam0.search (base=base0, expression=»(&(objectCategory=person)(objectClass=user))», attrs=['*','unicodePwd']) my_log (»\tNEW USR COUNT: %s» % len (res)) for r in res: dn = chgDc (str (r.dn)) if dn in users: continue sd = ndr_unpack (security.dom_sid, r['objectSid'][0]) (group_dom_sid, rid) = sd.split () if rid <= minRid: my_log( '!! MinRid ERR : ', r['sAMAccountName'][0] ) continue m = {"dn": dn, "objectClass": "user"} rp = mk_fill_matrix(m,r) rp('userPrincipalName',1) rp('sAMAccountName') rp('sn') rp('name') rp('initials') rp('displayName') rp('scriptPath') rp('description') rp('userAccountControl') rp('pwdLastSet') m["nTSecurityDescriptor"] = r['objectSid'] if 'maildom' in globals(): m["mail"] = "%s@%s" % (r['sAMAccountName'][0], maildom) if 'homeDirectory' in globals(): m["homeDirectory"] = r"%s\%s" % (homeDirectory,r['sAMAccountName'][0]) if 'homeDrive' in globals(): m["homeDrive"] = homeDrive sam.transaction_start() try: sam.add(m) except: sam.transaction_cancel() my_log( '!!! ERROR ADD USER : %s' % m['sAMAccountName']) else: sam.transaction_commit() # Copy the password for it if not 'unicodePwd' in r: my_log( '!!! NOT PASSWD FOR USER : %s' % m['sAMAccountName']) continue setpw = """ dn: %s changetype: modify replace: unicodePwd unicodePwd:: %s """ % (dn, base64.b64encode(str(r['unicodePwd']))) sam.transaction_start() try: sam.modify_ldif(setpw,["local_oid:1.3.6.1.4.1.7165.4.3.12:0"]) except: sam.transaction_cancel() my_log( '!!! ERROR SET PASSWORD USER : %s' % m['sAMAccountName']) else: sam.transaction_commit()

def cp_grp (): # перенос групп grps = [str (r.dn) for r in sam.search (base=base, expression=»(objectClass=group)», attrs=[])] res = sam0.search (base=base0, expression=»(&(objectClass=group)(objectCategory=Group))») my_log (»\tNEW GRP COUNT: %s» % len (res)) for r in usn_sort (res): dn = chgDc (str (r.dn)) if dn in grps: continue sd = ndr_unpack (security.dom_sid, r['objectSid'][0]) (group_dom_sid, rid) = sd.split () if rid <= minRid: my_log( '!! MinRid ERR : ', r['name'][0] ) continue m = {"dn": dn, "objectClass": "group"} rp = mk_fill_matrix(m,r) rp('sAMAccountName') rp('groupType') rp('description') m["nTSecurityDescriptor"] = r['objectSid'] sam.transaction_start() try: sam.add(m) except: sam.transaction_cancel() my_log( '!!! ERROR add GRP %s !!!' % m['sAMAccountName']) else: sam.transaction_commit() def grp_fill(): #заполнить группы членами my_log( "\tgrp_fill") grps ={} for r in sam.search(base=base,expression="(&(objectClass=group)(objectCategory=Group))",attrs=['member']): grps[str(r.dn)] = r['member'] if 'member' in r else [] users = [str(r.dn) for r in sam.search(base=base,expression="(&(objectCategory=person)(objectClass=user))", attrs=[])] for r in sam0.search(base=base0,expression="(&(objectClass=group)(objectCategory=Group))",attrs=['member']): if not 'member' in r: continue grp = chgDc(str(r.dn)) if not grps.has_key(grp): my_log( "!!not found group:\t",grp) continue add_m = '' for m in r['member']: m = chgDc(m) if m in grps[grp]: continue if not m in users: try: sam.search(base=m, attrs=[]) except: my_log( "!? err (not found) add %s \tto %s" % (m,grp)) continue add_m += "add: member\nmember: %s\n" % (m) if add_m == '': continue add_m = "\ndn: %s\nchangetype: modify\n%s\n" % (grp,add_m) sam.transaction_start() try: sam.modify_ldif(add_m) except: sam.transaction_cancel() my_log( "!!!Error fill grp "+grp) else: sam.transaction_commit()

def set_max_gid_uid (max_gid=start_unix_id, max_uid=start_unix_id): # установить max GID, UID в системе my_log ('set_max_gid_uid start: set max_gid=%s, max_uid=%s' % (max_gid, max_uid)) chg = '' r = sam.search (base=sfu)[0] for x in (['msSFU30MaxGidNumber', max_gid],['msSFU30MaxUidNumber', max_uid]): x[1] = max (x[1], start_unix_id) if not x[0] in r or x[1] > int (r[x[0]][0]): chg += «replace: %s\n%s: %d\n» % (x[0], x[0], x[1]) if chg!= '': chg = »\ndn: %s\nchangetype: modify\n%s\n» % (sfu, chg) sam.transaction_start () try: sam.modify_ldif (chg) except: sam.transaction_cancel () my_log (»!!! Error set msSFU30Max…») else: sam.transaction_commit () def get_next_uid (): # забрать следующий UID в системе nm = 'msSFU30MaxUidNumber' r = sam.search (base=sfu)[0] x = start_unix_id if nm in r: x = max (x, int (r[nm][0])) sam.transaction_start () try: sam.modify_ldif (»\ndn: %s\nchangetype: modify\nreplace: %s\n%s: %d\n» % (sfu, nm, nm, x+1)) except: sam.transaction_cancel () my_log (»!!! Error set msSFU30Max…») raise else: sam.transaction_commit () return x def set_max_id (): #найти max GID, UID, использованные в системе, и установить +1 max_gid = max ([int (r['gidNumber'][0]) for r in sam.search (base=base, expression=»(&(objectClass=group)(gidNumber=*))», attrs=['gidNumber'])]+[0]) max_uid = max ([int (r['uidNumber'][0]) for r in sam.search (base=base, expression=»(&(objectCategory=person)(objectClass=user)(uidNumber=*))», attrs=['uidNumber'])]+[0]) set_max_gid_uid (max_gid=max_gid+1, max_uid=max_uid+1) def check_id (): #проверить отсутств. GID, UID и заполнить r = sam.search (base=sfu)[0] (max_gid, max_uid) = [int (r[x][0]) if x in r else start_unix_id for x in ('msSFU30MaxGidNumber','msSFU30MaxUidNumber')] my_log ('check_id start: initial max_gid=%d, max_uid=%d' % (max_gid, max_uid)) sam.transaction_start () try: for r in rid_sort (sam.search (base=base, expression=»(&(objectClass=group)(!(gidNumber=*)))», attrs=['sAMAccountName','objectSid'])): set_grp_gid (r, max_gid) max_gid += 1 for r in usn_sort (sam.search (base=base, expression=»(&(objectCategory=person)(objectClass=user)(!(uidNumber=*)))», attrs=['sAMAccountName','uSNCreated'])): set_usr_gid_uid (r, max_uid) max_uid += 1 except: sam.transaction_cancel () my_log ('!!! ERROR check_id %s' % r['sAMAccountName']) else: sam.transaction_commit () set_max_gid_uid (max_gid=max_gid, max_uid=max_uid)

def set_max_rid (): #найти max RID в системе, установить корректные «RID Set» и «RID Manager» res = sam.search (base=base, expression=»(&(sAMAccountName=*)(objectSid=*))», attrs=[«objectSid»]) my_log (»\tSID COUNT: %s» % len (res)) x = max ([int (unpack ('

def mk_dom (): # Инициализация нового домена на samba 4 с прежним SID, сброшенной проверкой сложности пароля и паролем administrator = »1» !!! from samba.netcmd.main import cmd_sambatool def cmd (args, subcom='domain'): cmd = cmd_sambatool () try: retval = cmd._run («samba-tool», subcom, *args) except SystemExit, e: retval = e.code except Exception, e: cmd.show_command_error (e) retval = 1 if retval: sys.exit (retval) cmd (('provision', '--host-name=%s' % host, '--realm=%s' % realm, '--domain=%s' % dom, '--domain-sid=%s' % get_1out ('./get_dom_sid.py'), '--adminpass=UJHkjhm7KH$$2vrXy', '--function-level=2003', '--server-role=dc', '--use-rfc2307', '--dns-backend=SAMBA_INTERNAL')) cmd (('passwordsettings', 'set', '--complexity=off', '--history-length=0', '--min-pwd-length=0', '--min-pwd-age=0', '--max-pwd-age=0')) cmd (('setpassword', 'administrator', '--newpassword=1'), subcom='user') # сброс пароля на 1. Не забыть потом установить нормальный! Здесь присутствует вызов get_dom_sid.py как внешней программы в функции mk_dom () — инициализация нового домена.get_dom_sid.py просто печатает SID старого домена: #!/usr/bin/env python from lib1 import mk_sam0 print mk_sam0().domain_sid Так пришлось сделать, поскольку при подключении к базе старого домена в том же потоке, что и создание нового происходило замещение переменных окружения старыми данными. Итак, после того, как установлены все необходимые пакеты (samba4, sssd и зависимости), скопированы в нужные места каталоги старых баз, можем начать создание нового домена.

Инициализация домена — запуск mk_dom.py:

#!/usr/bin/env python # -*- coding: utf-8 -*- from lib1 import *

set_my_log () mk_dom () Если все прошло нормально (лог-файл имеет имя log_%y-%m-%d_%H:%M:%S.txt) смотрим smb.conf, временно добавляем в секции [global]dns forwarder = (на время перевода рабочих станций в новый домен).Копируем krb5.conf из /var/lib/samba/private в /etc (или делаем символический линк). Далее запускаем скрипт копирования объектов старого домена cp_dom.py:

#!/usr/bin/env python # -*- coding: utf-8 -*- from lib1 import *

set_my_log () my_log (base0,' →', base, dom, realm) mk_chg () #создать функцию преобразования имен mk_sam0() #подключение к старой базе mk_sam () #подключение к новой базе cp_ou () #копирование organizationalUnit cp_grp () #копирование групп cp_usr () #копирование пользователей grp_fill () #заполнение групп членами get_map0() #заполнить матрицу соответствия sAMAccountName <-> UID (с учетом наличия idmap Samba3) map_grp () #заполнить unix GID для групп map_usr () #заполнить unix GID, UID для пользователей set_max_rid () #найти max RID в системе, установить корректные «RID Set» и «RID Manager» set_max_id () #найти max GID, UID, использованные в системе, и установить +1 для следующих check_id () #проверить отсутствующие GID, UID и заполнить В лог файл неизбежно валятся ошибки. Наиболее серьезные — с тремя знаками »!» впереди. Типа:!!! ERROR ADD USER: 5CA6ADDF-A2C8–46E5-A!!! ERROR SET PASSWORD USER: 5CA6ADDF-A2C8–46E5-AЧаще всего это объекты, отсутствующие в текущей схеме и по сути ненужные. Если в домене были пользователи из другого домена леса, их добавление тоже не получится и попадет в лог. Несущественны ошибки типа ! MinRid ERR: Пользователи DCOMЕсли все терпимо, стартуем Samba:

start samba-ad-dc Немного о настройках sssd Детально описано здесь (Method 1: Connecting to AD via Kerberos).Готовим керберос для sssd: samba-tool domain exportkeytab /etc/krb5.sssd.keytab --principal=$ chown root: root /etc/krb5.sssd.keytab chmod 600 /etc/krb5.sssd.keytab Файл /etc/sssd/sssd.conf: [sssd] services = nss, pam config_file_version = 2 domains = newdom.lan

[nss]

[pam]

[domain/newdom.lan] id_provider = ad auth_provider = ad ldap_schema = ad krb5_keytab = /etc/krb5.sssd.keytab access_provider = ad ldap_id_mapping=false enumerate = true Сброс кэш sssd: sss_cache -GU Перезапуск sssd: restart sssd Кстати, сброс кэш sssd может не помочь при больших изменениях AD. Тогда надо, при остановленном sssd, удалить каталог из /var/lib/sss/ и восстановить их пустую структуру (из установочного пакета). Проверяем отображение пользователей и групп (база sssd заполняется некоторое время): getent passwd getent group Перетащить пользователей проще всего утилитой netdom.exe (netdom.exe move /?), добавив ее в логон-скрипт на старом сервере. Только надо запускать подходящую для ОС версию netdom.exe. Поскольку SID, GID, UID и пароли пользователей сохраняются, то перемещение почти прозрачно для пользователей — локальные папки остаются с ними, сетевые ресурсы тоже. Надо только переименовать домен в конфигурации файлового сервера Samba.У меня было еще проще — поскольку весь это зоопарк жил под OpenVZ, сетевой ресурс на отдельной ФС легко монтировался к разным файловым серверам одновременно (можно и DC сделать файловым сервером — нормально), а проблемы доступа автоматически решались соответствием GID и UID.

Объекты Group Policy не переносились в новый домен.

© Habrahabr.ru