259e3268c6bd293ca58d8db9ab4a18880e841ced
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

1) #!/usr/bin/python
2) 
3) #
4) # Script to check LDAP syncrepl replication state between two servers.
5) # One server is consider as provider and the other as consumer.
6) #
7) # This script can check replication state with two method :
8) #  - by the fisrt, entryCSN of all entries of LDAP directory will be
9) #    compare between two servers
10) #  - by the second, all values of all atributes of all entries will
11) #    be compare between two servers.
Benjamin Renard Added README file on provid...

Benjamin Renard authored 11 years ago

12) #
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

13) # In all case, contextCSN of servers will be compare and entries not
Benjamin Renard Added README file on provid...

Benjamin Renard authored 11 years ago

14) # present in consumer or in provider will be notice. You can decide to
15) # disable contextCSN verification by using argument --no-check-contextCSN.
16) #
17) # This script is also able to "touch" LDAP object on provider to force
18) # synchronisation of this object. This mechanism consist to add '%%TOUCH%%'
19) # value to an attribute of this object and remove it just after. The
20) # touched attribute is specify by parameter --touch. Of course, couple of
21) # DN and password provided, must have write right on this attribute.
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

22) #
Benjamin Renard Add informations about --re...

Benjamin Renard authored 11 years ago

23) # If your prefer, you can use --replace-touch parameter to replace value
24) # of touched attribute instead of adding the touched value. Use-ful in
25) # case of single-value attribute.
26) #
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

27) # This script could be use as Nagios plugin (-n argument)
28) #
29) # Requirement : 
30) # A single couple of DN and password able to connect to both server 
31) # and without restriction to retrieve objects from servers.
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

32) #
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

33) # Author : Benjamin Renard <brenard@easter-eggs.com>
34) # Date : Mon, 10 Dec 2012 18:04:24 +0100
35) # Source : http://git.zionetrix.net/check_syncrepl_extended
36) #
37) 
38) import ldap
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

39) import ldap.modlist as modlist
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

40) import logging
41) import sys
42) 
43) from optparse import OptionParser
44) 
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

45) TOUCH_VALUE='%%TOUCH%%'
46) 
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

47) parser = OptionParser(version="%prog version 1.0\n\nDate : Mon, 10 Dec 2012 18:04:24 +0100\nAuthor : Benjamin Renard <brenard@easter-eggs.com>\nSource : http://git.zionetrix.net/check_syncrepl_extended")
48) 
49) parser.add_option(	"-p", "--provider",
50) 			dest="provider",
51) 			action="store",
52) 			type='string',
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

53) 			help="LDAP provider URI (example : ldaps://ldapmaster.foo:636)")
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

54) 
55) parser.add_option(	"-c", "--consumer",
56) 			dest="consumer",
57) 			action="store",
58) 			type='string',
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

59) 			help="LDAP consumer URI (example : ldaps://ldapslave.foo:636)")
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

60) 
61) parser.add_option(	"-D", "--dn",
62) 			dest="dn",
63) 			action="store",
64) 			type='string',
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

65) 			help="LDAP bind DN (example : uid=nagios,ou=sysaccounts,o=example")
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

66) 
67) parser.add_option(	"-P", "--pwd",
68) 			dest="pwd",
69) 			action="store",
70) 			type='string',
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

71) 			help="LDAP bind password")
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

72) 
73) parser.add_option(	"-b", "--basedn",
74) 			dest="basedn",
75) 			action="store",
76) 			type='string',
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

77) 			help="LDAP base DN (example : o=example)")
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

78) 
79) parser.add_option(	"-f", "--filter",
80) 			dest="filter",
81) 			action="store",
82) 			type='string',
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

83) 			help="LDAP filter (default : (objectClass=*))",
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

84) 			default='(objectClass=*)')
85) 
86) parser.add_option(	"-d", "--debug",
87) 			dest="debug",
88) 			action="store_true",
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

89) 			help="Debug mode",
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

90) 			default=False)
91) 
92) parser.add_option(	"-n", "--nagios",
93) 			dest="nagios",
94) 			action="store_true",
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

95) 			help="Nagios check plugin mode",
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

96) 			default=False)
97) 
98) parser.add_option(	"-q", "--quiet",
99) 			dest="quiet",
100) 			action="store_true",
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

101) 			help="Quiet mode",
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

102) 			default=False)
103) 
104) parser.add_option(	"--no-check-certificate",
105) 			dest="nocheckcert",
106) 			action="store_true",
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

107) 			help="Don't check the server certificate (Default : False)",
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

108) 			default=False)
109) 
110) parser.add_option(	"--no-check-contextCSN",
111) 			dest="nocheckcontextcsn",
112) 			action="store_true",
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

113) 			help="Don't check servers contextCSN (Default : False)",
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

114) 			default=False)
115) 
116) parser.add_option(	"-a", "--attributes",
117) 			dest="attrs",
118) 			action="store_true",
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

119) 			help="Check attributes values (Default : check only entryCSN)",
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

120) 			default=False)
121) 
Benjamin Renard Added --exclude-attributes...

Benjamin Renard authored 11 years ago

122) parser.add_option(	"--exclude-attributes",
123) 			dest="excl_attrs",
124) 			action="store",
125) 			type='string',
126) 			help="Don't check this attribut (only in attribute check mode)",
127) 			default=None)
128) 
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

129) parser.add_option(	"--touch",
130) 			dest="touch",
131) 			action="store",
132) 			type='string',
133) 			help="Touch attribute giving in parameter to force resync a this LDAP object from provider. A value '%s' will be add to this attribute and remove after. The user use to connect to the LDAP directory must have write permission on this attribute on each object." % TOUCH_VALUE,
134) 			default=None)
135) 
Benjamin Renard Add --replace-touch parameter

Benjamin Renard authored 11 years ago

136) parser.add_option(	"--replace-touch",
137) 			dest="replacetouch",
138) 			action="store_true",
Benjamin Renard Add informations about --re...

Benjamin Renard authored 11 years ago

139) 			help="In touch mode, replace value instead of adding.",
Benjamin Renard Add --replace-touch parameter

Benjamin Renard authored 11 years ago

140) 			default=False)
Benjamin Renard Added --exclude-attributes...

Benjamin Renard authored 11 years ago

141) 
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

142) (options, args) = parser.parse_args()
143) 
144) if not options.provider or not options.consumer:
145) 	print "You must provide provider and customer URI"
146) 	sys.exit(1)
147) 
148) if not options.dn or not options.pwd:
149) 	print "You must provide DN and password to connect to LDAP servers"
150) 	sys.exit(1)
151) 
152) if not options.basedn:
153) 	print "You must provide base DN of connection to LDAP servers"
154) 	sys.exit(1)
155) 
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

156) if options.touch and not options.attrs:
157) 	logging.info('Force option attrs on touch mode')
158) 	options.attrs=True
159) 
Benjamin Renard Added --exclude-attributes...

Benjamin Renard authored 11 years ago

160) excl_attrs=[]
161) if options.excl_attrs:
162) 	for ex in options.excl_attrs.split(','):
163) 		excl_attrs.append(ex.strip())
164) 
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

165) FORMAT="%(asctime)s - %(levelname)s : %(message)s"
166) 
167) if options.debug:
168) 	logging.basicConfig(level=logging.DEBUG,format=FORMAT)
169) 	ldap.set_option(ldap.OPT_DEBUG_LEVEL,0)
170) 	ldapmodule_trace_level = 1
171) 	ldapmodule_trace_file = sys.stderr
172) elif options.nagios:
173) 	logging.basicConfig(level=logging.ERROR,format=FORMAT)
174) elif options.quiet:
175) 	logging.basicConfig(level=logging.WARNING,format=FORMAT)
176) else:
177) 	logging.basicConfig(level=logging.INFO,format=FORMAT)
178) 
179) class LdapServer(object):
180) 
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

181) 	uri = ""
182) 	dn = ""
183) 	pwd = ""
184) 
185) 	con = 0
186) 
187) 	def __init__(self,uri,dn,pwd):
188) 		self.uri = uri
189) 		self.dn   = dn
190) 		self.pwd  = pwd
191) 
192) 	def connect(self):
193) 		if self.con == 0:
194) 			try:
195) 				con = ldap.initialize(self.uri)
196) 				con.protocol_version = ldap.VERSION3
197) 				if self.dn != '':
198) 					con.simple_bind_s(self.dn,self.pwd)
199) 				self.con = con
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

200) 				return True
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

201) 			except ldap.LDAPError, e:
202) 				logging.error("LDAP Error : %s" % e)
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

203) 				return
204) 
205) 	def getContextCSN(self,basedn):
206) 		data=self.search(basedn,'(objectclass=*)',['contextCSN'])
207) 		if len(data)>0:
208) 			return data[0][0][1]['contextCSN'][0]
209) 		else:
210) 			return False
211) 
Benjamin Renard Fixed indent

Benjamin Renard authored 11 years ago

212) 	def search(self,basedn,filter,attrs):
213) 		res_id = self.con.search(basedn,ldap.SCOPE_SUBTREE,filter,attrs)
214) 		ret = []
215) 		while 1:
216) 			res_type, res_data = self.con.result(res_id,0)
217) 			if res_data == []:
218) 				break
219) 			else:
220) 				if res_type == ldap.RES_SEARCH_ENTRY:
221) 					ret.append(res_data)
222) 		return ret
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

223) 
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

224) 	def update_object(self,dn,old,new):
225) 		ldif = modlist.modifyModlist(old,new)
226) 		if ldif == []:
227) 			return True
228) 		try:
229) 			logging.debug('Update object %s : %s' % (dn,ldif))
230) 			self.con.modify_s(dn,ldif)
231) 			return True
232) 		except ldap.LDAPError, e:
233) 			logging.error('Error updating object %s : %s' % (dn,e))
234) 		return False
235) 
236) 	def get_attr(self,obj,attr):
237) 		if attr in obj[0][1]:
238) 			return obj[0][1][attr]
239) 		return []
240) 
241) 	def touch_object(self,dn,attr,orig_value):
Benjamin Renard Add --replace-touch parameter

Benjamin Renard authored 11 years ago

242) 		if options.replacetouch:
Benjamin Renard Fixed error in replace touc...

Benjamin Renard authored 11 years ago

243) 			new_value=[TOUCH_VALUE]
Benjamin Renard Add --replace-touch parameter

Benjamin Renard authored 11 years ago

244) 		else:
245) 			new_value=list(orig_value)
246) 			new_value.append(TOUCH_VALUE)
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

247) 		try:
248) 			logging.info('Add value "%s" to attribute %s of object %s' % (TOUCH_VALUE,attr,dn))
249) 			if self.update_object(dn,{attr: orig_value}, {attr: new_value}):
250) 				logging.info('Remove value "%s" to attribute %s of object %s' % (TOUCH_VALUE,attr,dn))
251) 				self.update_object(dn,{attr: new_value}, {attr: orig_value})
252) 				return True
253) 		except ldap.LDAPError, e:
254) 			logging.error('Error touching object %s : %s' % (dn,e))
255) 		return False
256) 
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

257) if options.nocheckcert:
258) 	ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT,ldap.OPT_X_TLS_NEVER)
259) 
260) servers=[options.provider,options.consumer]
261) 
262) LdapServers={}
263) LdapObjects={}
264) LdapServersCSN={}
265) 
266) for srv in servers:
267) 	logging.info('Connect to %s' % srv)
268) 	LdapServers[srv]=LdapServer(srv,options.dn,options.pwd)
269) 
270) 	if not LdapServers[srv].connect():
271) 		if options.nagios:
272) 			print "UNKWNON - Failed to connect to %s" % srv
273) 			sys.exit(3)
274) 		else:
275) 			sys.exit(1)
276) 
277) 	if not options.nocheckcontextcsn:
278) 		LdapServersCSN[srv]=LdapServers[srv].getContextCSN(options.basedn)
279) 		logging.info('ContextCSN of %s : %s' % (srv, LdapServersCSN[srv]))
280) 
281) 	logging.info('List objects from %s' % srv)
282) 	LdapObjects[srv]={}
283) 
284) 	if options.attrs:
285) 		for obj in LdapServers[srv].search(options.basedn,options.filter,[]):
286) 			logging.debug('Found on %s : %s' % (srv,obj[0][0]))
287) 			LdapObjects[srv][obj[0][0]]=obj[0][1]
288) 	else:
289) 		for obj in LdapServers[srv].search(options.basedn,options.filter,['entryCSN']):
290) 			logging.debug('Found on %s : %s / %s' % (srv,obj[0][0],obj[0][1]['entryCSN'][0]))
291) 			LdapObjects[srv][obj[0][0]]=obj[0][1]['entryCSN'][0]
292) 
293) 	logging.info('%s objects founds' % len(LdapObjects[srv]))
294) 
295) 
296) not_found={}
297) not_sync={}
298) 
299) for srv in servers:
300) 	not_found[srv]=[]
301) 	not_sync[srv]=[]
302) 
303) if options.attrs:
304) 	logging.info("Check if objects a are synchronized (by comparing attributes's values)")
305) else:
306) 	logging.info('Check if objets are synchronized (by comparing entryCSN)')
307) for obj in LdapObjects[options.provider]:
308) 	logging.debug('Check obj %s' % (obj))
309) 	for srv in LdapObjects:
310) 		if srv == options.provider:
311) 			continue
312) 		if obj in LdapObjects[srv]:
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

313) 			touch=False
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

314) 			if LdapObjects[options.provider][obj] != LdapObjects[srv][obj]:
315) 				if options.attrs:
316) 					attrs_list=[]
317) 					for attr in LdapObjects[options.provider][obj]:
Benjamin Renard Added --exclude-attributes...

Benjamin Renard authored 11 years ago

318) 						if attr in excl_attrs:
319) 							continue
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

320) 						if attr not in LdapObjects[srv][obj]:
321) 							attrs_list.append(attr)
Benjamin Renard Specify sync problem during...

Benjamin Renard authored 11 years ago

322) 							logging.debug("Obj %s not synchronized : %s not present on %s" % (obj,','.join(attrs_list),srv))
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

323) 							touch=True
Benjamin Renard Fixed attributes check : ad...

Benjamin Renard authored 11 years ago

324) 						else:
325) 							LdapObjects[srv][obj][attr].sort()
326) 							LdapObjects[options.provider][obj][attr].sort()
327) 							if LdapObjects[srv][obj][attr]!=LdapObjects[options.provider][obj][attr]:
328) 								attrs_list.append(attr)
329) 								logging.debug("Obj %s not synchronized : %s not same value(s)" % (obj,','.join(attrs_list)))
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

330) 								touch=True
Benjamin Renard Fixed attributes check : ad...

Benjamin Renard authored 11 years ago

331) 					if len(attrs_list)>0:
332) 						not_sync[srv].append("%s (%s)" % (obj,','.join(attrs_list)))
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

333) 				else:
334) 					logging.debug("Obj %s not synchronized : %s <-> %s" % (obj,LdapObjects[options.provider][obj],LdapObjects[srv][obj]))
335) 					not_sync[srv].append(obj)
Benjamin Renard Added "Touch attribute" fea...

Benjamin Renard authored 11 years ago

336) 			if touch and options.touch:
337) 				orig_value=[]
338) 				if options.touch in LdapObjects[options.provider][obj]:
339) 					orig_value=LdapObjects[options.provider][obj][options.touch]
340) 				LdapServers[options.provider].touch_object(obj,options.touch,orig_value)
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

341) 		else:
342) 			logging.debug('Obj %s : not found on %s' % (obj,srv))
343) 			not_found[srv].append(obj)
Benjamin Renard Added touch on not found ob...

Benjamin Renard authored 11 years ago

344) 			if options.touch:
345) 				orig_value=[]
346) 				if options.touch in LdapObjects[options.provider][obj]:
347) 					orig_value=LdapObjects[options.provider][obj][options.touch]
348) 				LdapServers[options.provider].touch_object(obj,options.touch,orig_value)
Benjamin Renard Initial commit

Benjamin Renard authored 12 years ago

349) 
350) for obj in LdapObjects[options.consumer]:
351) 	logging.debug('Check obj %s of consumer' % obj)
352) 	if obj not in LdapObjects[options.provider]:
353) 		logging.debug('Obj %s : not found on provider' % obj)
354) 		not_found[options.provider].append(obj)
355) 
356) if options.nagios:
357) 	errors=[]
358) 
359) 	if not options.nocheckcontextcsn:
360) 		for srv in LdapServersCSN:
361) 			if srv==options.provider:
362) 				continue
363) 			if LdapServersCSN[srv]!=LdapServersCSN[options.provider]:
364) 				errors.append('ContextCSN of %s not the same of provider' % srv)
365) 
366) 	if len(not_found[options.consumer])>0:
367) 		errors.append("%s not found object(s) on consumer" % len(not_found[options.consumer]))
368) 	if len(not_found[options.provider])>0:
369) 		errors.append("%s not found object(s) on provider" % len(not_found[options.provider]))
370) 	if len(not_sync[options.consumer])>0:
371) 		errors.append("%s not synchronized object(s) on consumer" % len(not_sync[options.consumer]))
372) 	if len(errors)>0:
373) 		print "CRITICAL : "+', '.join(errors)
374) 		sys.exit(2)
375) 	else:
376) 		print 'OK : consumer and provider are synchronized'
377) 		sys.exit(0)
378) else:
379) 	noerror=True
380) 	for srv in servers:
Benjamin Renard Fixed ContextCSN check duri...

Benjamin Renard authored 11 years ago

381) 		if not options.nocheckcontextcsn:
382) 			if srv==options.provider:
383) 				continue
384) 			if LdapServersCSN[srv]!=LdapServersCSN[options.provider]:
385) 				logging.warning('ContextCSN of %s not the same of provider' % srv)
386) 				noerror=False
387)