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 - 图1

安全 kerberos - 图2

安全 kerberos - 图3

很久很久以前,在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

  1. import threading
  2. class krb5lock(object):
  3. """Thread lock to serialize kerberos authentication request"""
  4. # storage for the instance reference
  5. __instance = None
  6. def __init__(self):
  7. """ Singleton """
  8. if krb5lock.__instance is None:
  9. krb5lock.__instance = threading.Lock()
  10. self.__dict__['_krb5lock__instance'] = krb5lock.__instance
  11. def __getattr__(self, attr):
  12. """ Delegate access to implementation """
  13. return getattr(krb5lock.__instance, attr)
  14. def __setattr__(self, attr, value):
  15. """ Delegate access to implementation """
  16. return setattr(krb5lock.__instance, attr, value)

ad_conn.py

  1. import ldap3
  2. import ssl
  3. import krb5
  4. import os
  5. import tempfile
  6. from krb5lock import *
  7. class test_kerberos():
  8. def __init__(self):
  9. self.realm = 'DEMO2016.COM'
  10. self.hostname = 'ddei-comm0086.demo2016.com'
  11. # self.hostname = '10.204.253.14'
  12. self.ip = '10.204.253.14'
  13. self.port = 636
  14. self.use_ssl = True
  15. self.basedn='dc=demo2016,dc=com'
  16. self.user = "administrator@demo2016.com"
  17. self.password = "Trend#200"
  18. self.kdc = self.ip + ':88'
  19. self.kpasswd = self.ip + ':464'
  20. self._ldap_server = None
  21. self._ldap = None
  22. self._config_name = None
  23. self._ccache_name = None
  24. def _common_connect(self):
  25. self._ldap_server = ldap3.Server(host=self.hostname,
  26. port=self.port,
  27. get_info=ldap3.ALL,
  28. use_ssl=self.use_ssl,
  29. tls=ldap3.Tls(validate=ssl.CERT_NONE,
  30. version=ssl.PROTOCOL_SSLv23),
  31. connect_timeout=3)
  32. self._ldap = ldap3.Connection(self._ldap_server,
  33. user=str(self.user),
  34. password=str(self.password),
  35. authentication=ldap3.SIMPLE,
  36. auto_bind=False,
  37. collect_usage=True)
  38. self._ldap.bind()
  39. def search(self, filter):
  40. res = self._ldap.search(self.basedn, filter)
  41. print(self._ldap.entries)
  42. def _generate_tmp(self):
  43. fd, self._ccache_name = tempfile.mkstemp()
  44. os.close(fd)
  45. fd, self._config_name = tempfile.mkstemp()
  46. os.close(fd)
  47. print("create _ccache_name: " + self._ccache_name + "\n")
  48. print("create _config_name: " + self._config_name + "\n")
  49. fout = file(self._config_name, 'w')
  50. fout.write('# Auto generated krb5.conf\n')
  51. fout.write('[libdefaults]\n')
  52. fout.write(' default_realm = %s\n' % self.realm)
  53. fout.write(' dns_lookup_kdc = yes\n')
  54. fout.write(' dns_lookup_realm = yes\n')
  55. fout.write(' rdns = yes\n')
  56. fout.write(' default_tgs_enctypes = rc4-hmac\n')
  57. fout.write(' default_tkt_enctypes = rc4-hmac\n')
  58. fout.write('[realms]\n')
  59. fout.write(' %s = {\n' % self.realm)
  60. kdclist = self.kdc.split()
  61. for kdc in kdclist:
  62. fout.write(' kdc = %s\n' % kdc)
  63. kpasswdlist = self.kpasswd.split()
  64. for kpasswd in kpasswdlist:
  65. fout.write(' kpasswd_server = %s\n' % kpasswd)
  66. fout.write(' }\n')
  67. fout.close()
  68. def _set_environ(self, name, value):
  69. if value is None:
  70. del os.environ[name]
  71. else:
  72. os.environ[name] = value
  73. def _kerberos_connect(self):
  74. self._close()
  75. if self._ldap is not None:
  76. self._ldap.unbind()
  77. tls = ldap3.Tls(validate=ssl.CERT_NONE, version=ssl.PROTOCOL_SSLv23)
  78. 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)
  79. lock = krb5lock()
  80. try:
  81. lock.acquire()
  82. self._generate_tmp()
  83. self._set_environ('KRB5CCNAME', self._ccache_name)
  84. self._set_environ('KRB5_CONFIG', self._config_name)
  85. try:
  86. krb5.get_init_creds_password('{0}@{1}'.format(self.user.split('@')[0], self.realm), self.password)
  87. except krb5.Error as e:
  88. print(e)
  89. finally:
  90. lock.release()
  91. # ldap3 Connection using TGT
  92. self._ldap = ldap3.Connection(self._ldap_server,
  93. authentication=ldap3.SASL,
  94. sasl_mechanism=ldap3.KERBEROS,
  95. auto_bind=False,
  96. auto_referrals=True,
  97. collect_usage=True)
  98. self._ldap.bind()
  99. def _close(self):
  100. if self._ldap is not None:
  101. self._ldap.unbind()
  102. if self._ccache_name is not None:
  103. os.unlink(self._ccache_name)
  104. self._ccache_name = None
  105. if self._config_name is not None:
  106. os.unlink(self._config_name)
  107. self._config_name = None
  108. if __name__ == '__main__':
  109. ker_ad = test_kerberos()
  110. # ker_ad._kerberos_connect()
  111. ker_ad._common_connect()
  112. ker_ad.search('(objectclass=person)')
  113. ker_ad._close()

conf文件

  1. # Auto generated krb5.conf
  2. [libdefaults]
  3. default_realm = DEMO2016.COM
  4. dns_lookup_kdc = yes
  5. dns_lookup_realm = yes
  6. rdns = yes
  7. default_tgs_enctypes = rc4-hmac
  8. default_tkt_enctypes = rc4-hmac
  9. [realms]
  10. DEMO2016.COM = {
  11. kdc = 10.204.253.14:88
  12. kpasswd_server = 10.204.253.14:464
  13. }