]>
git.proxmox.com Git - ovs.git/blob - debian/ovs-monitor-ipsec
2 # Copyright (c) 2009, 2010 Nicira Networks
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:
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
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.
22 # - Doesn't actually check that Interface is connected to bridge
23 # - Doesn't support cert authentication
27 import logging
, logging
.handlers
33 from ovs
.db
import error
34 from ovs
.db
import types
40 # By default log messages as DAEMON into syslog
41 s_log
= logging
.getLogger("ovs-monitor-ipsec")
42 l_handler
= logging
.handlers
.SysLogHandler(
44 facility
=logging
.handlers
.SysLogHandler
.LOG_DAEMON
)
45 l_formatter
= logging
.Formatter('%(filename)s: %(levelname)s: %(message)s')
46 l_handler
.setFormatter(l_formatter
)
47 s_log
.addHandler(l_handler
)
50 setkey
= "/usr/sbin/setkey"
52 # Class to configure the racoon daemon, which handles IKE negotiation
54 # Default locations for files
55 conf_file
= "/etc/racoon/racoon.conf"
56 cert_file
= "/etc/racoon/certs"
57 psk_file
= "/etc/racoon/psk.txt"
59 # Default racoon configuration file we use for IKE
60 conf_template
= """# Configuration file generated by Open vSwitch
62 # Do not modify by hand!
64 path pre_shared_key "/etc/racoon/psk.txt";
65 path certificate "/etc/racoon/certs";
71 encryption_algorithm aes;
73 authentication_method pre_shared_key;
81 encryption_algorithm aes;
82 authentication_algorithm hmac_sha1, hmac_md5;
83 compression_algorithm deflate;
91 # Replace racoon's conf file with our template
92 f
= open(Racoon
.conf_file
, "w")
93 f
.write(Racoon
.conf_template
)
96 # Clear out any pre-shared keys
102 exitcode
= subprocess
.call(["/etc/init.d/racoon", "reload"])
104 s_log
.warning("couldn't reload racoon")
106 def commit_psk(self
):
107 f
= open(Racoon
.psk_file
, 'w')
109 # The file must only be accessible by root
110 os
.chmod(Racoon
.psk_file
, stat
.S_IRUSR | stat
.S_IWUSR
)
112 f
.write("# Generated by Open vSwitch...do not modify by hand!\n\n")
113 for host
, psk
in self
.psk_hosts
.iteritems():
114 f
.write("%s %s\n" % (host
, psk
))
117 def add_psk(self
, host
, psk
):
118 self
.psk_hosts
[host
] = psk
121 def del_psk(self
, host
):
122 if host
in self
.psk_hosts
:
123 del self
.psk_hosts
[host
]
127 # Class to configure IPsec on a system using racoon for IKE and setkey
128 # for maintaining the Security Association Database (SAD) and Security
129 # Policy Database (SPD). Only policies for GRE are supported.
134 self
.racoon
= Racoon()
136 def call_setkey(self
, cmds
):
138 p
= subprocess
.Popen([setkey
, "-c"], stdin
=subprocess
.PIPE
,
139 stdout
=subprocess
.PIPE
)
141 s_log
.error("could not call setkey")
144 # xxx It is safer to pass the string into the communicate()
145 # xxx method, but it didn't work for slightly longer commands.
146 # xxx An alternative may need to be found.
148 return p
.communicate()[0]
150 def get_spi(self
, local_ip
, remote_ip
, proto
="esp"):
151 # Run the setkey dump command to retrieve the SAD. Then, parse
152 # the output looking for SPI buried in the output. Note that
153 # multiple SAD entries can exist for the same "flow", since an
154 # older entry could be in a "dying" state.
156 host_line
= "%s %s" % (local_ip
, remote_ip
)
157 results
= self
.call_setkey("dump ;").split("\n")
158 for i
in range(len(results
)):
159 if results
[i
].strip() == host_line
:
160 # The SPI is in the line following the host pair
161 spi_line
= results
[i
+1]
162 if (spi_line
[1:4] == proto
):
163 spi
= spi_line
.split()[2]
164 spi_list
.append(spi
.split('(')[1].rstrip(')'))
168 self
.call_setkey("flush;")
170 def sad_del(self
, local_ip
, remote_ip
):
171 # To delete all SAD entries, we should be able to use setkey's
172 # "deleteall" command. Unfortunately, it's fundamentally broken
173 # on Linux and not documented as such.
176 # Delete local_ip->remote_ip SAD entries
177 spi_list
= self
.get_spi(local_ip
, remote_ip
)
179 cmds
+= "delete %s %s esp %s;\n" % (local_ip
, remote_ip
, spi
)
181 # Delete remote_ip->local_ip SAD entries
182 spi_list
= self
.get_spi(remote_ip
, local_ip
)
184 cmds
+= "delete %s %s esp %s;\n" % (remote_ip
, local_ip
, spi
)
187 self
.call_setkey(cmds
)
190 self
.call_setkey("spdflush;")
192 def spd_add(self
, local_ip
, remote_ip
):
193 cmds
= ("spdadd %s %s gre -P out ipsec esp/transport//default;" %
194 (local_ip
, remote_ip
))
196 cmds
+= ("spdadd %s %s gre -P in ipsec esp/transport//default;" %
197 (remote_ip
, local_ip
))
198 self
.call_setkey(cmds
)
200 def spd_del(self
, local_ip
, remote_ip
):
201 cmds
= "spddelete %s %s gre -P out;" % (local_ip
, remote_ip
)
203 cmds
+= "spddelete %s %s gre -P in;" % (remote_ip
, local_ip
)
204 self
.call_setkey(cmds
)
206 def ipsec_cert_del(self
, local_ip
, remote_ip
):
207 # Need to support cert...right now only PSK supported
208 self
.racoon
.del_psk(remote_ip
)
209 self
.spd_del(local_ip
, remote_ip
)
210 self
.sad_del(local_ip
, remote_ip
)
212 def ipsec_cert_update(self
, local_ip
, remote_ip
, cert
):
213 # Need to support cert...right now only PSK supported
214 self
.racoon
.add_psk(remote_ip
, "abc12345")
215 self
.spd_add(local_ip
, remote_ip
)
217 def ipsec_psk_del(self
, local_ip
, remote_ip
):
218 self
.racoon
.del_psk(remote_ip
)
219 self
.spd_del(local_ip
, remote_ip
)
220 self
.sad_del(local_ip
, remote_ip
)
222 def ipsec_psk_update(self
, local_ip
, remote_ip
, psk
):
223 self
.racoon
.add_psk(remote_ip
, psk
)
224 self
.spd_add(local_ip
, remote_ip
)
227 def keep_table_columns(schema
, table_name
, column_types
):
228 table
= schema
.tables
.get(table_name
)
230 raise error
.Error("schema has no %s table" % table_name
)
233 for column_name
, column_type
in column_types
.iteritems():
234 column
= table
.columns
.get(column_name
)
236 raise error
.Error("%s table schema lacks %s column"
237 % (table_name
, column_name
))
238 if column
.type != column_type
:
239 raise error
.Error("%s column in %s table has type \"%s\", "
240 "expected type \"%s\""
241 % (column_name
, table_name
,
242 column
.type.toEnglish(),
243 column_type
.toEnglish()))
244 new_columns
[column_name
] = column
245 table
.columns
= new_columns
248 def monitor_uuid_schema_cb(schema
):
249 string_type
= types
.Type(types
.BaseType(types
.StringType
))
250 string_map_type
= types
.Type(types
.BaseType(types
.StringType
),
251 types
.BaseType(types
.StringType
),
255 new_tables
["Interface"] = keep_table_columns(
256 schema
, "Interface", {"name": string_type
,
258 "options": string_map_type
,
259 "other_config": string_map_type
})
260 schema
.tables
= new_tables
263 print "usage: %s [OPTIONS] DATABASE" % sys
.argv
[0]
264 print "where DATABASE is a socket on which ovsdb-server is listening."
266 print "Other options:"
267 print " -h, --help display this help message"
272 options
, args
= getopt
.gnu_getopt(
273 argv
[1:], 'h', ['help'] + ovs
.daemon
.LONG_OPTIONS
)
274 except getopt
.GetoptError
, geo
:
275 sys
.stderr
.write("%s: %s\n" % (ovs
.util
.PROGRAM_NAME
, geo
.msg
))
278 for key
, value
in options
:
279 if key
in ['-h', '--help']:
281 elif not ovs
.daemon
.parse_opt(key
, value
):
282 sys
.stderr
.write("%s: unhandled option %s\n"
283 % (ovs
.util
.PROGRAM_NAME
, key
))
287 sys
.stderr
.write("%s: exactly one nonoption argument is required "
288 "(use --help for help)\n" % ovs
.util
.PROGRAM_NAME
)
291 ovs
.daemon
.die_if_already_running()
294 idl
= ovs
.db
.idl
.Idl(remote
, "Open_vSwitch", monitor_uuid_schema_cb
)
296 ovs
.daemon
.daemonize()
303 poller
= ovs
.poller
.Poller()
309 for rec
in idl
.data
["Interface"].itervalues():
310 name
= rec
.name
.as_scalar()
311 ipsec_cert
= rec
.other_config
.get("ipsec_cert")
312 ipsec_psk
= rec
.other_config
.get("ipsec_psk")
313 is_ipsec
= ipsec_cert
or ipsec_psk
315 if rec
.type.as_scalar() == "gre" and is_ipsec
:
316 new_interfaces
[name
] = {
317 "remote_ip": rec
.options
.get("remote_ip"),
318 "local_ip": rec
.options
.get("local_ip", "0.0.0.0/0"),
319 "ipsec_cert": ipsec_cert
,
320 "ipsec_psk": ipsec_psk
}
322 if interfaces
!= new_interfaces
:
323 for name
, vals
in interfaces
.items():
324 if name
not in new_interfaces
.keys():
325 ipsec
.ipsec_cert_del(vals
["local_ip"], vals
["remote_ip"])
326 for name
, vals
in new_interfaces
.items():
327 if vals
== interfaces
.get(name
):
329 "configuration changed for %s, need to delete "
330 "interface first" % name
)
333 if vals
["ipsec_cert"]:
334 ipsec
.ipsec_cert_update(vals
["local_ip"],
335 vals
["remote_ip"], vals
["ipsec_cert"])
336 elif vals
["ipsec_psk"]:
337 ipsec
.ipsec_psk_update(vals
["local_ip"],
338 vals
["remote_ip"], vals
["ipsec_psk"])
341 "no ipsec_cert or ipsec_psk defined for %s" % name
)
344 interfaces
= new_interfaces
346 if __name__
== '__main__':
350 # Let system.exit() calls complete normally
353 s_log
.exception("traceback")
354 sys
.exit(ovs
.daemon
.RESTART_EXIT_CODE
)