https://www.programcreek.com/python/example/107946/ldap3.Server
https://www.programcreek.com/python/example/107948/ldap3.Connection
原理
https://blog.csdn.net/pzqingchong/article/details/79772404



很久很久以前,在Kerberos王国有一个神奇的王,它的名字叫KDC,国号为秦(域名)。
为了更好地管理臣民(用户)、管理营业性场所(文件共享服务器、邮件服务器、打印服务器等),要求臣民、营业性场所到王室领取一个账号,账号主要包括用户名/密码。
有一个臣民叫王老虎,账号名为“王老虎”,密码“xxxxxxxx”, 那么在Kerberos王国里,有几个人知道王老虎的密码?
一个是王老虎本人,另一个就是王,即KDC。还有其他人知道吧?没有了!
有一家提供文件共享的服务场所,名字叫“小美共享文件服务社”,密码是“xxxxxxx”,这个账号的密码,在KDC王国,只有2个人知道,一个是自己,另外一个就是王,KDC。
KDC王颁布以下规定:
(1)臣民去臣民家拜访,需要先到KDC票务中心买票,只有买到了被拜访者家的票(Service Ticket),才能前往拜访。
(2)臣民前去营业场所消费,同样需要先到KDC购票入场。
(3)营业场所去营业场所消费,一样需要购票入场。
现在王老虎想去“小美文件服务社”坐坐,并浏览一下文件,能直接冲进去吗?
不能。
王老虎先到KDC票务中心买票,票务中心说:请问你是哪位?
王老虎!
票务中心:请用王老虎的密钥加密“一段认证信息”发给我!
王老虎照做!
票务中心知道王老虎的密钥,成功解密王老虎的加密信息,认证成功!
上文说了,在这个世界上除了KDC知道王老虎的密钥,另外一个就是王老虎本人了。
既然KDC用王老虎的密钥解密成功,那说明加密信息的密钥肯定是王老虎的,间接表明买票的人,肯定是王老虎。
以上只是KDC验证王老虎的过程,问题来了,王老虎如何知道KDC票务中心不是假冒的?
很简单,玄机就在“一段认证信息”里了,这“一段认证信息”里,王老虎想写啥就写啥,王老虎是这样写的:
“知乎到底能走多远?”
由于这段信息是用王老虎的密钥发给KDC的,如果KDC是真的,那么自然可以解密到明文信息,然后把王老虎的“知乎到底能走多远?”返回给王老虎,那么王老虎就知道对方是真的KDC。
双向认证成功,可以避免任何一方是假冒的安全风险。
既然认证成功,那么KDC就为王老虎出票呗。KDC给王老虎2件东西:
(1)用王老虎密钥加密的session key
(2) “小美文件服务社”门票(Service Ticket)
这个门票是用“小美文件服务社”的密钥加密,里面包含 :
A) 和王老虎一样的session key
B) 门票持有人姓名:王老虎
C) 王老虎属于哪个Group
上文1、2 中的session key 是一样的。
接下来王老虎就直奔小美处了,王老虎敲敲小美的门,小美说:先生请出示您的门票。
王老虎出示2样东西:
(1)门票Ticket
(2)用session key 加密“一段认证信息”,认证信息里写道:“美女,你会说相声不?”
小美用自己的密钥解密ticket,得到上文ABC信息。
小美用解密得到的A = session key ,解密(2),得到明文认证信息:“美女,你会说相声不?”
小美对王老虎说:“美女,你会说相声不?”
王老虎窃喜,看来这个小美不是假冒的,否则不可能知道我加密的认证信息!
至此,双向认证成功,接下来就使用双方都知道的session key 来加密/解密双向的流量,由于session key 只有KDC、王老虎、小美知晓,任何第三方都无法知晓session key,所以无法解密流量。
上文忘记说了,小美的每一个文件都具有权限管理,小美根据上文的C,可以知道王老虎属于哪个group,看看这个group有没有被访问文件的“ read 、write 、modify、full control”权限,并依据特定权限,限制王老虎操作文件的特权。
Example
把运行python文件的机器的dns改成ad server的ip
把krb5.so跟以下两个文件krb5lock.py和ad_conn.py放到同一目录下
Realm要全部大写
使用kerberos认证时,有时候host不能用ip,要用FQDN
krb5lock.py
import threadingclass krb5lock(object):"""Thread lock to serialize kerberos authentication request"""# storage for the instance reference__instance = Nonedef __init__(self):""" Singleton """if krb5lock.__instance is None:krb5lock.__instance = threading.Lock()self.__dict__['_krb5lock__instance'] = krb5lock.__instancedef __getattr__(self, attr):""" Delegate access to implementation """return getattr(krb5lock.__instance, attr)def __setattr__(self, attr, value):""" Delegate access to implementation """return setattr(krb5lock.__instance, attr, value)
ad_conn.py
import ldap3import sslimport krb5import osimport tempfilefrom krb5lock import *class test_kerberos():def __init__(self):self.realm = 'DEMO2016.COM'self.hostname = 'ddei-comm0086.demo2016.com'# self.hostname = '10.204.253.14'self.ip = '10.204.253.14'self.port = 636self.use_ssl = Trueself.basedn='dc=demo2016,dc=com'self.user = "administrator@demo2016.com"self.password = "Trend#200"self.kdc = self.ip + ':88'self.kpasswd = self.ip + ':464'self._ldap_server = Noneself._ldap = Noneself._config_name = Noneself._ccache_name = Nonedef _common_connect(self):self._ldap_server = ldap3.Server(host=self.hostname,port=self.port,get_info=ldap3.ALL,use_ssl=self.use_ssl,tls=ldap3.Tls(validate=ssl.CERT_NONE,version=ssl.PROTOCOL_SSLv23),connect_timeout=3)self._ldap = ldap3.Connection(self._ldap_server,user=str(self.user),password=str(self.password),authentication=ldap3.SIMPLE,auto_bind=False,collect_usage=True)self._ldap.bind()def search(self, filter):res = self._ldap.search(self.basedn, filter)print(self._ldap.entries)def _generate_tmp(self):fd, self._ccache_name = tempfile.mkstemp()os.close(fd)fd, self._config_name = tempfile.mkstemp()os.close(fd)print("create _ccache_name: " + self._ccache_name + "\n")print("create _config_name: " + self._config_name + "\n")fout = file(self._config_name, 'w')fout.write('# Auto generated krb5.conf\n')fout.write('[libdefaults]\n')fout.write(' default_realm = %s\n' % self.realm)fout.write(' dns_lookup_kdc = yes\n')fout.write(' dns_lookup_realm = yes\n')fout.write(' rdns = yes\n')fout.write(' default_tgs_enctypes = rc4-hmac\n')fout.write(' default_tkt_enctypes = rc4-hmac\n')fout.write('[realms]\n')fout.write(' %s = {\n' % self.realm)kdclist = self.kdc.split()for kdc in kdclist:fout.write(' kdc = %s\n' % kdc)kpasswdlist = self.kpasswd.split()for kpasswd in kpasswdlist:fout.write(' kpasswd_server = %s\n' % kpasswd)fout.write(' }\n')fout.close()def _set_environ(self, name, value):if value is None:del os.environ[name]else:os.environ[name] = valuedef _kerberos_connect(self):self._close()if self._ldap is not None:self._ldap.unbind()tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_SSLv23)self._ldap_server = ldap3.Server(host=self.hostname, port=self.port, get_info=ldap3.ALL, use_ssl=self.use_ssl, tls=tls, connect_timeout=3)lock = krb5lock()try:lock.acquire()self._generate_tmp()self._set_environ('KRB5CCNAME', self._ccache_name)self._set_environ('KRB5_CONFIG', self._config_name)try:krb5.get_init_creds_password('{0}@{1}'.format(self.user.split('@')[0], self.realm), self.password)except krb5.Error as e:print(e)finally:lock.release()# ldap3 Connection using TGTself._ldap = ldap3.Connection(self._ldap_server,authentication=ldap3.SASL,sasl_mechanism=ldap3.KERBEROS,auto_bind=False,auto_referrals=True,collect_usage=True)self._ldap.bind()def _close(self):if self._ldap is not None:self._ldap.unbind()if self._ccache_name is not None:os.unlink(self._ccache_name)self._ccache_name = Noneif self._config_name is not None:os.unlink(self._config_name)self._config_name = Noneif __name__ == '__main__':ker_ad = test_kerberos()# ker_ad._kerberos_connect()ker_ad._common_connect()ker_ad.search('(objectclass=person)')ker_ad._close()
conf文件
# Auto generated krb5.conf[libdefaults]default_realm = DEMO2016.COMdns_lookup_kdc = yesdns_lookup_realm = yesrdns = yesdefault_tgs_enctypes = rc4-hmacdefault_tkt_enctypes = rc4-hmac[realms]DEMO2016.COM = {kdc = 10.204.253.14:88kpasswd_server = 10.204.253.14:464}
