]>
Commit | Line | Data |
---|---|---|
a3acf0b0 | 1 | #!/usr/bin/python |
00c08589 | 2 | # Copyright (c) 2009, 2010, 2011 Nicira Networks |
a3acf0b0 JP |
3 | # |
4 | # Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | # you may not use this file except in compliance with the License. | |
6 | # You may obtain a copy of the License at: | |
7 | # | |
8 | # http://www.apache.org/licenses/LICENSE-2.0 | |
9 | # | |
10 | # Unless required by applicable law or agreed to in writing, software | |
11 | # distributed under the License is distributed on an "AS IS" BASIS, | |
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | # See the License for the specific language governing permissions and | |
14 | # limitations under the License. | |
15 | ||
16 | ||
17 | # A daemon to monitor attempts to create GRE-over-IPsec tunnels. | |
18 | # Uses racoon and setkey to support the configuration. Assumes that | |
19 | # OVS has complete control over IPsec configuration for the box. | |
20 | ||
21 | # xxx To-do: | |
22 | # - Doesn't actually check that Interface is connected to bridge | |
3c52fa7b JP |
23 | # - If a certificate is badly formed, Racoon will refuse to start. We |
24 | # should do a better job of verifying certificates are valid before | |
25 | # adding an interface to racoon.conf. | |
a3acf0b0 JP |
26 | |
27 | ||
28 | import getopt | |
3c52fa7b | 29 | import glob |
0f4d9dce EJ |
30 | import logging |
31 | import logging.handlers | |
a3acf0b0 | 32 | import os |
3eda9831 | 33 | import socket |
a3acf0b0 JP |
34 | import subprocess |
35 | import sys | |
36 | ||
8cdf0349 | 37 | import ovs.dirs |
a3acf0b0 JP |
38 | from ovs.db import error |
39 | from ovs.db import types | |
40 | import ovs.util | |
41 | import ovs.daemon | |
42 | import ovs.db.idl | |
43 | ||
a3acf0b0 | 44 | s_log = logging.getLogger("ovs-monitor-ipsec") |
3eda9831 BP |
45 | try: |
46 | # By default log messages as DAEMON into syslog | |
47 | l_handler = logging.handlers.SysLogHandler( | |
48 | "/dev/log", | |
49 | facility=logging.handlers.SysLogHandler.LOG_DAEMON) | |
50 | l_formatter = logging.Formatter('%(filename)s: %(levelname)s: %(message)s') | |
51 | l_handler.setFormatter(l_formatter) | |
52 | s_log.addHandler(l_handler) | |
53 | except socket.error, e: | |
54 | logging.basicConfig() | |
55 | s_log.warn("failed to connect to syslog (%s)" % e) | |
a3acf0b0 JP |
56 | |
57 | setkey = "/usr/sbin/setkey" | |
58 | ||
0f4d9dce | 59 | |
a3acf0b0 JP |
60 | # Class to configure the racoon daemon, which handles IKE negotiation |
61 | class Racoon: | |
62 | # Default locations for files | |
63 | conf_file = "/etc/racoon/racoon.conf" | |
3c52fa7b | 64 | cert_dir = "/etc/racoon/certs" |
a3acf0b0 JP |
65 | psk_file = "/etc/racoon/psk.txt" |
66 | ||
3c52fa7b JP |
67 | # Racoon configuration header we use for IKE |
68 | conf_header = """# Configuration file generated by Open vSwitch | |
a3acf0b0 JP |
69 | # |
70 | # Do not modify by hand! | |
71 | ||
3c52fa7b JP |
72 | path pre_shared_key "%s"; |
73 | path certificate "%s"; | |
a3acf0b0 | 74 | |
3c52fa7b JP |
75 | """ |
76 | ||
77 | # Racoon configuration footer we use for IKE | |
78 | conf_footer = """sainfo anonymous { | |
79 | pfs_group 2; | |
80 | lifetime time 1 hour; | |
81 | encryption_algorithm aes; | |
82 | authentication_algorithm hmac_sha1, hmac_md5; | |
83 | compression_algorithm deflate; | |
84 | } | |
85 | ||
86 | """ | |
87 | ||
88 | # Certificate entry template. | |
89 | cert_entry = """remote %s { | |
a3acf0b0 | 90 | exchange_mode main; |
e97a1034 | 91 | nat_traversal on; |
73976ebd | 92 | ike_frag on; |
3c52fa7b JP |
93 | certificate_type x509 "%s" "%s"; |
94 | my_identifier asn1dn; | |
95 | peers_identifier asn1dn; | |
96 | peers_certfile x509 "%s"; | |
97 | verify_identifier on; | |
a3acf0b0 JP |
98 | proposal { |
99 | encryption_algorithm aes; | |
100 | hash_algorithm sha1; | |
3c52fa7b | 101 | authentication_method rsasig; |
a3acf0b0 JP |
102 | dh_group 2; |
103 | } | |
104 | } | |
105 | ||
3c52fa7b JP |
106 | """ |
107 | ||
108 | # Pre-shared key template. | |
109 | psk_entry = """remote %s { | |
110 | exchange_mode main; | |
111 | nat_traversal on; | |
112 | proposal { | |
113 | encryption_algorithm aes; | |
114 | hash_algorithm sha1; | |
115 | authentication_method pre_shared_key; | |
116 | dh_group 2; | |
117 | } | |
a3acf0b0 | 118 | } |
3c52fa7b | 119 | |
a3acf0b0 JP |
120 | """ |
121 | ||
122 | def __init__(self): | |
123 | self.psk_hosts = {} | |
124 | self.cert_hosts = {} | |
125 | ||
ef035eef JP |
126 | if not os.path.isdir(self.cert_dir): |
127 | os.mkdir(self.cert_dir) | |
128 | ||
3c52fa7b JP |
129 | # Clean out stale peer certs from previous runs |
130 | for ovs_cert in glob.glob("%s/ovs-*.pem" % self.cert_dir): | |
131 | try: | |
132 | os.remove(ovs_cert) | |
133 | except OSError: | |
134 | s_log.warning("couldn't remove %s" % ovs_cert) | |
a3acf0b0 | 135 | |
3c52fa7b JP |
136 | # Replace racoon's conf file with our template |
137 | self.commit() | |
a3acf0b0 JP |
138 | |
139 | def reload(self): | |
140 | exitcode = subprocess.call(["/etc/init.d/racoon", "reload"]) | |
141 | if exitcode != 0: | |
215d7280 | 142 | # Racoon is finicky about its configuration file and will |
3c52fa7b JP |
143 | # refuse to start if it sees something it doesn't like |
144 | # (e.g., a certificate file doesn't exist). Try restarting | |
145 | # the process before giving up. | |
146 | s_log.warning("attempting to restart racoon") | |
147 | exitcode = subprocess.call(["/etc/init.d/racoon", "restart"]) | |
148 | if exitcode != 0: | |
149 | s_log.warning("couldn't reload racoon") | |
150 | ||
151 | def commit(self): | |
152 | # Rewrite the Racoon configuration file | |
153 | conf_file = open(self.conf_file, 'w') | |
154 | conf_file.write(Racoon.conf_header % (self.psk_file, self.cert_dir)) | |
155 | ||
156 | for host, vals in self.cert_hosts.iteritems(): | |
157 | conf_file.write(Racoon.cert_entry % (host, vals["certificate"], | |
158 | vals["private_key"], vals["peer_cert_file"])) | |
159 | ||
160 | for host in self.psk_hosts: | |
161 | conf_file.write(Racoon.psk_entry % host) | |
162 | ||
163 | conf_file.write(Racoon.conf_footer) | |
164 | conf_file.close() | |
165 | ||
166 | # Rewrite the pre-shared keys file; it must only be readable by root. | |
167 | orig_umask = os.umask(0077) | |
168 | psk_file = open(Racoon.psk_file, 'w') | |
169 | os.umask(orig_umask) | |
170 | ||
171 | psk_file.write("# Generated by Open vSwitch...do not modify by hand!") | |
172 | psk_file.write("\n\n") | |
173 | for host, vals in self.psk_hosts.iteritems(): | |
174 | psk_file.write("%s %s\n" % (host, vals["psk"])) | |
175 | psk_file.close() | |
a3acf0b0 | 176 | |
3c52fa7b | 177 | self.reload() |
a3acf0b0 | 178 | |
3c52fa7b JP |
179 | def _add_psk(self, host, psk): |
180 | if host in self.cert_hosts: | |
181 | raise error.Error("host %s already defined for cert" % host) | |
a3acf0b0 | 182 | |
a3acf0b0 | 183 | self.psk_hosts[host] = psk |
3c52fa7b JP |
184 | self.commit() |
185 | ||
186 | def _verify_certs(self, vals): | |
187 | # Racoon will refuse to start if the certificate files don't | |
188 | # exist, so verify that they're there. | |
189 | if not os.path.isfile(vals["certificate"]): | |
190 | raise error.Error("'certificate' file does not exist: %s" | |
191 | % vals["certificate"]) | |
192 | elif not os.path.isfile(vals["private_key"]): | |
193 | raise error.Error("'private_key' file does not exist: %s" | |
194 | % vals["private_key"]) | |
195 | ||
196 | # Racoon won't start if a given certificate or private key isn't | |
197 | # valid. This is a weak test, but will detect the most flagrant | |
198 | # errors. | |
199 | if vals["peer_cert"].find("-----BEGIN CERTIFICATE-----") == -1: | |
200 | raise error.Error("'peer_cert' is not in valid PEM format") | |
201 | ||
202 | cert = open(vals["certificate"]).read() | |
203 | if cert.find("-----BEGIN CERTIFICATE-----") == -1: | |
204 | raise error.Error("'certificate' is not in valid PEM format") | |
205 | ||
206 | cert = open(vals["private_key"]).read() | |
207 | if cert.find("-----BEGIN RSA PRIVATE KEY-----") == -1: | |
208 | raise error.Error("'private_key' is not in valid PEM format") | |
3c52fa7b JP |
209 | |
210 | def _add_cert(self, host, vals): | |
a3acf0b0 | 211 | if host in self.psk_hosts: |
3c52fa7b JP |
212 | raise error.Error("host %s already defined for psk" % host) |
213 | ||
ef7ee76a | 214 | if vals["certificate"] == None: |
3c52fa7b | 215 | raise error.Error("'certificate' not defined for %s" % host) |
ef7ee76a | 216 | elif vals["private_key"] == None: |
0f4d9dce | 217 | # Assume the private key is stored in the same PEM file as |
3c52fa7b JP |
218 | # the certificate. We make a copy of "vals" so that we don't |
219 | # modify the original "vals", which would cause the script | |
220 | # to constantly think that the configuration has changed | |
221 | # in the database. | |
222 | vals = vals.copy() | |
223 | vals["private_key"] = vals["certificate"] | |
224 | ||
225 | self._verify_certs(vals) | |
226 | ||
227 | # The peer's certificate comes to us in PEM format as a string. | |
228 | # Write that string to a file for Racoon to use. | |
229 | peer_cert_file = "%s/ovs-%s.pem" % (self.cert_dir, host) | |
230 | f = open(peer_cert_file, "w") | |
231 | f.write(vals["peer_cert"]) | |
232 | f.close() | |
233 | ||
234 | vals["peer_cert_file"] = peer_cert_file | |
235 | ||
236 | self.cert_hosts[host] = vals | |
237 | self.commit() | |
238 | ||
239 | def _del_cert(self, host): | |
240 | peer_cert_file = self.cert_hosts[host]["peer_cert_file"] | |
241 | del self.cert_hosts[host] | |
242 | self.commit() | |
243 | try: | |
244 | os.remove(peer_cert_file) | |
245 | except OSError: | |
246 | pass | |
247 | ||
248 | def add_entry(self, host, vals): | |
249 | if vals["peer_cert"]: | |
250 | self._add_cert(host, vals) | |
251 | elif vals["psk"]: | |
252 | self._add_psk(host, vals) | |
253 | ||
254 | def del_entry(self, host): | |
255 | if host in self.cert_hosts: | |
256 | self._del_cert(host) | |
257 | elif host in self.psk_hosts: | |
a3acf0b0 | 258 | del self.psk_hosts[host] |
3c52fa7b | 259 | self.commit() |
a3acf0b0 JP |
260 | |
261 | ||
262 | # Class to configure IPsec on a system using racoon for IKE and setkey | |
263 | # for maintaining the Security Association Database (SAD) and Security | |
264 | # Policy Database (SPD). Only policies for GRE are supported. | |
265 | class IPsec: | |
266 | def __init__(self): | |
267 | self.sad_flush() | |
268 | self.spd_flush() | |
269 | self.racoon = Racoon() | |
3c52fa7b | 270 | self.entries = [] |
a3acf0b0 JP |
271 | |
272 | def call_setkey(self, cmds): | |
273 | try: | |
0f4d9dce | 274 | p = subprocess.Popen([setkey, "-c"], stdin=subprocess.PIPE, |
a3acf0b0 JP |
275 | stdout=subprocess.PIPE) |
276 | except: | |
277 | s_log.error("could not call setkey") | |
278 | sys.exit(1) | |
279 | ||
280 | # xxx It is safer to pass the string into the communicate() | |
281 | # xxx method, but it didn't work for slightly longer commands. | |
282 | # xxx An alternative may need to be found. | |
283 | p.stdin.write(cmds) | |
284 | return p.communicate()[0] | |
285 | ||
286 | def get_spi(self, local_ip, remote_ip, proto="esp"): | |
287 | # Run the setkey dump command to retrieve the SAD. Then, parse | |
288 | # the output looking for SPI buried in the output. Note that | |
289 | # multiple SAD entries can exist for the same "flow", since an | |
290 | # older entry could be in a "dying" state. | |
291 | spi_list = [] | |
292 | host_line = "%s %s" % (local_ip, remote_ip) | |
293 | results = self.call_setkey("dump ;").split("\n") | |
294 | for i in range(len(results)): | |
295 | if results[i].strip() == host_line: | |
296 | # The SPI is in the line following the host pair | |
0f4d9dce | 297 | spi_line = results[i + 1] |
a3acf0b0 JP |
298 | if (spi_line[1:4] == proto): |
299 | spi = spi_line.split()[2] | |
300 | spi_list.append(spi.split('(')[1].rstrip(')')) | |
301 | return spi_list | |
302 | ||
303 | def sad_flush(self): | |
304 | self.call_setkey("flush;") | |
305 | ||
306 | def sad_del(self, local_ip, remote_ip): | |
307 | # To delete all SAD entries, we should be able to use setkey's | |
308 | # "deleteall" command. Unfortunately, it's fundamentally broken | |
309 | # on Linux and not documented as such. | |
310 | cmds = "" | |
311 | ||
312 | # Delete local_ip->remote_ip SAD entries | |
313 | spi_list = self.get_spi(local_ip, remote_ip) | |
314 | for spi in spi_list: | |
315 | cmds += "delete %s %s esp %s;\n" % (local_ip, remote_ip, spi) | |
316 | ||
317 | # Delete remote_ip->local_ip SAD entries | |
318 | spi_list = self.get_spi(remote_ip, local_ip) | |
319 | for spi in spi_list: | |
320 | cmds += "delete %s %s esp %s;\n" % (remote_ip, local_ip, spi) | |
321 | ||
322 | if cmds: | |
323 | self.call_setkey(cmds) | |
324 | ||
325 | def spd_flush(self): | |
326 | self.call_setkey("spdflush;") | |
327 | ||
328 | def spd_add(self, local_ip, remote_ip): | |
f916d1cc | 329 | cmds = ("spdadd %s %s gre -P out ipsec esp/transport//require;\n" % |
a3acf0b0 | 330 | (local_ip, remote_ip)) |
f916d1cc | 331 | cmds += ("spdadd %s %s gre -P in ipsec esp/transport//require;" % |
a3acf0b0 JP |
332 | (remote_ip, local_ip)) |
333 | self.call_setkey(cmds) | |
334 | ||
335 | def spd_del(self, local_ip, remote_ip): | |
3c52fa7b | 336 | cmds = "spddelete %s %s gre -P out;\n" % (local_ip, remote_ip) |
a3acf0b0 JP |
337 | cmds += "spddelete %s %s gre -P in;" % (remote_ip, local_ip) |
338 | self.call_setkey(cmds) | |
339 | ||
3c52fa7b JP |
340 | def add_entry(self, local_ip, remote_ip, vals): |
341 | if remote_ip in self.entries: | |
342 | raise error.Error("host %s already configured for ipsec" | |
343 | % remote_ip) | |
a3acf0b0 | 344 | |
3c52fa7b | 345 | self.racoon.add_entry(remote_ip, vals) |
a3acf0b0 JP |
346 | self.spd_add(local_ip, remote_ip) |
347 | ||
3c52fa7b | 348 | self.entries.append(remote_ip) |
a3acf0b0 | 349 | |
3c52fa7b JP |
350 | def del_entry(self, local_ip, remote_ip): |
351 | if remote_ip in self.entries: | |
352 | self.racoon.del_entry(remote_ip) | |
353 | self.spd_del(local_ip, remote_ip) | |
354 | self.sad_del(local_ip, remote_ip) | |
355 | ||
356 | self.entries.remove(remote_ip) | |
a3acf0b0 JP |
357 | |
358 | ||
359 | def keep_table_columns(schema, table_name, column_types): | |
360 | table = schema.tables.get(table_name) | |
361 | if not table: | |
362 | raise error.Error("schema has no %s table" % table_name) | |
363 | ||
364 | new_columns = {} | |
365 | for column_name, column_type in column_types.iteritems(): | |
366 | column = table.columns.get(column_name) | |
367 | if not column: | |
368 | raise error.Error("%s table schema lacks %s column" | |
369 | % (table_name, column_name)) | |
370 | if column.type != column_type: | |
371 | raise error.Error("%s column in %s table has type \"%s\", " | |
372 | "expected type \"%s\"" | |
373 | % (column_name, table_name, | |
374 | column.type.toEnglish(), | |
375 | column_type.toEnglish())) | |
376 | new_columns[column_name] = column | |
377 | table.columns = new_columns | |
378 | return table | |
0f4d9dce EJ |
379 | |
380 | ||
8cdf0349 | 381 | def prune_schema(schema): |
a3acf0b0 | 382 | string_type = types.Type(types.BaseType(types.StringType)) |
ef7ee76a | 383 | optional_ssl_type = types.Type(types.BaseType(types.UuidType, |
0f4d9dce | 384 | ref_table_name='SSL'), None, 0, 1) |
a3acf0b0 JP |
385 | string_map_type = types.Type(types.BaseType(types.StringType), |
386 | types.BaseType(types.StringType), | |
387 | 0, sys.maxint) | |
0f4d9dce | 388 | |
a3acf0b0 JP |
389 | new_tables = {} |
390 | new_tables["Interface"] = keep_table_columns( | |
391 | schema, "Interface", {"name": string_type, | |
392 | "type": string_type, | |
e16a28b5 | 393 | "options": string_map_type}) |
ef7ee76a JP |
394 | new_tables["Open_vSwitch"] = keep_table_columns( |
395 | schema, "Open_vSwitch", {"ssl": optional_ssl_type}) | |
396 | new_tables["SSL"] = keep_table_columns( | |
397 | schema, "SSL", {"certificate": string_type, | |
398 | "private_key": string_type}) | |
a3acf0b0 JP |
399 | schema.tables = new_tables |
400 | ||
0f4d9dce | 401 | |
a3acf0b0 JP |
402 | def usage(): |
403 | print "usage: %s [OPTIONS] DATABASE" % sys.argv[0] | |
404 | print "where DATABASE is a socket on which ovsdb-server is listening." | |
405 | ovs.daemon.usage() | |
406 | print "Other options:" | |
407 | print " -h, --help display this help message" | |
408 | sys.exit(0) | |
0f4d9dce EJ |
409 | |
410 | ||
3c52fa7b JP |
411 | def update_ipsec(ipsec, interfaces, new_interfaces): |
412 | for name, vals in interfaces.iteritems(): | |
413 | if name not in new_interfaces: | |
414 | ipsec.del_entry(vals["local_ip"], vals["remote_ip"]) | |
415 | ||
416 | for name, vals in new_interfaces.iteritems(): | |
417 | orig_vals = interfaces.get(name) | |
418 | if orig_vals: | |
419 | # Configuration for this host already exists. Check if it's | |
420 | # changed. | |
421 | if vals == orig_vals: | |
422 | continue | |
423 | else: | |
424 | ipsec.del_entry(vals["local_ip"], vals["remote_ip"]) | |
425 | ||
426 | try: | |
427 | ipsec.add_entry(vals["local_ip"], vals["remote_ip"], vals) | |
428 | except error.Error, msg: | |
429 | s_log.warning("skipping ipsec config for %s: %s" % (name, msg)) | |
430 | ||
0f4d9dce | 431 | |
ef7ee76a | 432 | def get_ssl_cert(data): |
8cdf0349 BP |
433 | for ovs_rec in data["Open_vSwitch"].rows.itervalues(): |
434 | ssl = ovs_rec.ssl | |
435 | if ssl and ssl.certificate and ssl.private_key: | |
436 | return (ssl.certificate, ssl.private_key) | |
ef7ee76a JP |
437 | |
438 | return None | |
439 | ||
0f4d9dce | 440 | |
a3acf0b0 JP |
441 | def main(argv): |
442 | try: | |
443 | options, args = getopt.gnu_getopt( | |
6acddcaa | 444 | argv[1:], 'h', ['help'] + ovs.daemon.LONG_OPTIONS) |
a3acf0b0 JP |
445 | except getopt.GetoptError, geo: |
446 | sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg)) | |
447 | sys.exit(1) | |
0f4d9dce | 448 | |
a3acf0b0 JP |
449 | for key, value in options: |
450 | if key in ['-h', '--help']: | |
451 | usage() | |
452 | elif not ovs.daemon.parse_opt(key, value): | |
453 | sys.stderr.write("%s: unhandled option %s\n" | |
454 | % (ovs.util.PROGRAM_NAME, key)) | |
455 | sys.exit(1) | |
0f4d9dce | 456 | |
a3acf0b0 JP |
457 | if len(args) != 1: |
458 | sys.stderr.write("%s: exactly one nonoption argument is required " | |
459 | "(use --help for help)\n" % ovs.util.PROGRAM_NAME) | |
460 | sys.exit(1) | |
461 | ||
a3acf0b0 | 462 | remote = args[0] |
8cdf0349 BP |
463 | |
464 | schema_file = "%s/vswitch.ovsschema" % ovs.dirs.PKGDATADIR | |
465 | schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schema_file)) | |
466 | prune_schema(schema) | |
467 | idl = ovs.db.idl.Idl(remote, schema) | |
a3acf0b0 JP |
468 | |
469 | ovs.daemon.daemonize() | |
470 | ||
471 | ipsec = IPsec() | |
472 | ||
473 | interfaces = {} | |
474 | while True: | |
475 | if not idl.run(): | |
476 | poller = ovs.poller.Poller() | |
477 | idl.wait(poller) | |
478 | poller.block() | |
479 | continue | |
ef7ee76a | 480 | |
8cdf0349 | 481 | ssl_cert = get_ssl_cert(idl.tables) |
0f4d9dce | 482 | |
a3acf0b0 | 483 | new_interfaces = {} |
8cdf0349 BP |
484 | for rec in idl.tables["Interface"].rows.itervalues(): |
485 | if rec.type == "ipsec_gre": | |
486 | name = rec.name | |
487 | options = rec.options | |
ef7ee76a | 488 | entry = { |
8cdf0349 BP |
489 | "remote_ip": options.get("remote_ip"), |
490 | "local_ip": options.get("local_ip", "0.0.0.0/0"), | |
491 | "certificate": options.get("certificate"), | |
492 | "private_key": options.get("private_key"), | |
493 | "use_ssl_cert": options.get("use_ssl_cert"), | |
494 | "peer_cert": options.get("peer_cert"), | |
0f4d9dce | 495 | "psk": options.get("psk")} |
a3acf0b0 | 496 | |
ef7ee76a | 497 | if entry["peer_cert"] and entry["psk"]: |
0f4d9dce | 498 | s_log.warning("both 'peer_cert' and 'psk' defined for %s" |
3c52fa7b JP |
499 | % name) |
500 | continue | |
ef7ee76a | 501 | elif not entry["peer_cert"] and not entry["psk"]: |
0f4d9dce | 502 | s_log.warning("no 'peer_cert' or 'psk' defined for %s" |
3c52fa7b JP |
503 | % name) |
504 | continue | |
a3acf0b0 | 505 | |
ef7ee76a JP |
506 | # The "use_ssl_cert" option is deprecated and will |
507 | # likely go away in the near future. | |
508 | if entry["use_ssl_cert"] == "true": | |
509 | if not ssl_cert: | |
510 | s_log.warning("no valid SSL entry for %s" % name) | |
511 | continue | |
512 | ||
513 | entry["certificate"] = ssl_cert[0] | |
514 | entry["private_key"] = ssl_cert[1] | |
515 | ||
516 | new_interfaces[name] = entry | |
0f4d9dce | 517 | |
3c52fa7b JP |
518 | if interfaces != new_interfaces: |
519 | update_ipsec(ipsec, interfaces, new_interfaces) | |
a3acf0b0 | 520 | interfaces = new_interfaces |
0f4d9dce EJ |
521 | |
522 | ||
a3acf0b0 JP |
523 | if __name__ == '__main__': |
524 | try: | |
525 | main(sys.argv) | |
526 | except SystemExit: | |
527 | # Let system.exit() calls complete normally | |
528 | raise | |
529 | except: | |
530 | s_log.exception("traceback") | |
55f8a832 | 531 | sys.exit(ovs.daemon.RESTART_EXIT_CODE) |