vici: flush-certs command flushes certificate cache
[strongswan.git] / src / libcharon / plugins / vici / python / vici / session.py
1 import collections
2 import socket
3
4 from .exception import SessionException, CommandException, EventUnknownException
5 from .protocol import Transport, Packet, Message
6
7
8 class Session(object):
9 def __init__(self, sock=None):
10 if sock is None:
11 sock = socket.socket(socket.AF_UNIX)
12 sock.connect("/var/run/charon.vici")
13 self.handler = SessionHandler(Transport(sock))
14
15 def version(self):
16 """Retrieve daemon and system specific version information.
17
18 :return: daemon and system specific version information
19 :rtype: dict
20 """
21 return self.handler.request("version")
22
23 def stats(self):
24 """Retrieve IKE daemon statistics and load information.
25
26 :return: IKE daemon statistics and load information
27 :rtype: dict
28 """
29 return self.handler.request("stats")
30
31 def reload_settings(self):
32 """Reload strongswan.conf settings and any plugins supporting reload.
33 """
34 self.handler.request("reload-settings")
35
36 def initiate(self, sa):
37 """Initiate an SA.
38
39 :param sa: the SA to initiate
40 :type sa: dict
41 :return: generator for logs emitted as dict
42 :rtype: generator
43 """
44 return self.handler.streamed_request("initiate", "control-log", sa)
45
46 def terminate(self, sa):
47 """Terminate an SA.
48
49 :param sa: the SA to terminate
50 :type sa: dict
51 :return: generator for logs emitted as dict
52 :rtype: generator
53 """
54 return self.handler.streamed_request("terminate", "control-log", sa)
55
56 def redirect(self, sa):
57 """Redirect an IKE_SA.
58
59 :param sa: the SA to redirect
60 :type sa: dict
61 """
62 self.handler.request("redirect", sa)
63
64 def install(self, policy):
65 """Install a trap, drop or bypass policy defined by a CHILD_SA config.
66
67 :param policy: policy to install
68 :type policy: dict
69 """
70 self.handler.request("install", policy)
71
72 def uninstall(self, policy):
73 """Uninstall a trap, drop or bypass policy defined by a CHILD_SA config.
74
75 :param policy: policy to uninstall
76 :type policy: dict
77 """
78 self.handler.request("uninstall", policy)
79
80 def list_sas(self, filters=None):
81 """Retrieve active IKE_SAs and associated CHILD_SAs.
82
83 :param filters: retrieve only matching IKE_SAs (optional)
84 :type filters: dict
85 :return: generator for active IKE_SAs and associated CHILD_SAs as dict
86 :rtype: generator
87 """
88 return self.handler.streamed_request("list-sas", "list-sa", filters)
89
90 def list_policies(self, filters=None):
91 """Retrieve installed trap, drop and bypass policies.
92
93 :param filters: retrieve only matching policies (optional)
94 :type filters: dict
95 :return: generator for installed trap, drop and bypass policies as dict
96 :rtype: generator
97 """
98 return self.handler.streamed_request("list-policies", "list-policy",
99 filters)
100
101 def list_conns(self, filters=None):
102 """Retrieve loaded connections.
103
104 :param filters: retrieve only matching configuration names (optional)
105 :type filters: dict
106 :return: generator for loaded connections as dict
107 :rtype: generator
108 """
109 return self.handler.streamed_request("list-conns", "list-conn",
110 filters)
111
112 def get_conns(self):
113 """Retrieve connection names loaded exclusively over vici.
114
115 :return: connection names
116 :rtype: dict
117 """
118 return self.handler.request("get-conns")
119
120 def list_certs(self, filters=None):
121 """Retrieve loaded certificates.
122
123 :param filters: retrieve only matching certificates (optional)
124 :type filters: dict
125 :return: generator for loaded certificates as dict
126 :rtype: generator
127 """
128 return self.handler.streamed_request("list-certs", "list-cert", filters)
129
130 def load_conn(self, connection):
131 """Load a connection definition into the daemon.
132
133 :param connection: connection definition
134 :type connection: dict
135 """
136 self.handler.request("load-conn", connection)
137
138 def unload_conn(self, name):
139 """Unload a connection definition.
140
141 :param name: connection definition name
142 :type name: dict
143 """
144 self.handler.request("unload-conn", name)
145
146 def load_cert(self, certificate):
147 """Load a certificate into the daemon.
148
149 :param certificate: PEM or DER encoded certificate
150 :type certificate: dict
151 """
152 self.handler.request("load-cert", certificate)
153
154 def load_key(self, private_key):
155 """Load a private key into the daemon.
156
157 :param private_key: PEM or DER encoded key
158 """
159 self.handler.request("load-key", private_key)
160
161 def load_shared(self, secret):
162 """Load a shared IKE PSK, EAP or XAuth secret into the daemon.
163
164 :param secret: shared IKE PSK, EAP or XAuth secret
165 :type secret: dict
166 """
167 self.handler.request("load-shared", secret)
168
169 def flush_certs(self, filter=None):
170 """Flush the volatile certificate cache.
171
172 Flush the certificate stored temporarily in the cache. The filter
173 allows to flush only a certain type of certificates, e.g. CRLs.
174
175 :param filter: flush only certificates of a given type (optional)
176 :type filter: dict
177 """
178 self.handler.request("flush-certs", filter)
179
180 def clear_creds(self):
181 """Clear credentials loaded over vici.
182
183 Clear all loaded certificate, private key and shared key credentials.
184 This affects only credentials loaded over vici, but additionally
185 flushes the credential cache.
186 """
187 self.handler.request("clear-creds")
188
189 def load_pool(self, pool):
190 """Load a virtual IP pool.
191
192 Load an in-memory virtual IP and configuration attribute pool.
193 Existing pools with the same name get updated, if possible.
194
195 :param pool: virtual IP and configuration attribute pool
196 :type pool: dict
197 """
198 return self.handler.request("load-pool", pool)
199
200 def unload_pool(self, pool_name):
201 """Unload a virtual IP pool.
202
203 Unload a previously loaded virtual IP and configuration attribute pool.
204 Unloading fails for pools with leases currently online.
205
206 :param pool_name: pool by name
207 :type pool_name: dict
208 """
209 self.handler.request("unload-pool", pool_name)
210
211 def get_pools(self):
212 """Retrieve loaded pools.
213
214 :return: loaded pools
215 :rtype: dict
216 """
217 return self.handler.request("get-pools")
218
219 def listen(self, event_types):
220 """Register and listen for the given events.
221
222 :param event_types: event types to register
223 :type event_types: list
224 :return: generator for streamed event responses as (event_type, dict)
225 :rtype: generator
226 """
227 return self.handler.listen(event_types)
228
229
230 class SessionHandler(object):
231 """Handles client command execution requests over vici."""
232
233 def __init__(self, transport):
234 self.transport = transport
235
236 def _communicate(self, packet):
237 """Send packet over transport and parse response.
238
239 :param packet: packet to send
240 :type packet: :py:class:`vici.protocol.Packet`
241 :return: parsed packet in a tuple with message type and payload
242 :rtype: :py:class:`collections.namedtuple`
243 """
244 self.transport.send(packet)
245 return Packet.parse(self.transport.receive())
246
247 def _register_unregister(self, event_type, register):
248 """Register or unregister for the given event.
249
250 :param event_type: event to register
251 :type event_type: str
252 :param register: whether to register or unregister
253 :type register: bool
254 """
255 if register:
256 packet = Packet.register_event(event_type)
257 else:
258 packet = Packet.unregister_event(event_type)
259 response = self._communicate(packet)
260 if response.response_type == Packet.EVENT_UNKNOWN:
261 raise EventUnknownException(
262 "Unknown event type '{event}'".format(event=event_type)
263 )
264 elif response.response_type != Packet.EVENT_CONFIRM:
265 raise SessionException(
266 "Unexpected response type {type}, "
267 "expected '{confirm}' (EVENT_CONFIRM)".format(
268 type=response.response_type,
269 confirm=Packet.EVENT_CONFIRM,
270 )
271 )
272
273 def request(self, command, message=None):
274 """Send request with an optional message.
275
276 :param command: command to send
277 :type command: str
278 :param message: message (optional)
279 :type message: str
280 :return: command result
281 :rtype: dict
282 """
283 if message is not None:
284 message = Message.serialize(message)
285 packet = Packet.request(command, message)
286 response = self._communicate(packet)
287
288 if response.response_type != Packet.CMD_RESPONSE:
289 raise SessionException(
290 "Unexpected response type {type}, "
291 "expected '{response}' (CMD_RESPONSE)".format(
292 type=response.response_type,
293 response=Packet.CMD_RESPONSE
294 )
295 )
296
297 command_response = Message.deserialize(response.payload)
298 if "success" in command_response:
299 if command_response["success"] != b"yes":
300 raise CommandException(
301 "Command failed: {errmsg}".format(
302 errmsg=command_response["errmsg"]
303 )
304 )
305
306 return command_response
307
308 def streamed_request(self, command, event_stream_type, message=None):
309 """Send command request and collect and return all emitted events.
310
311 :param command: command to send
312 :type command: str
313 :param event_stream_type: event type emitted on command execution
314 :type event_stream_type: str
315 :param message: message (optional)
316 :type message: str
317 :return: generator for streamed event responses as dict
318 :rtype: generator
319 """
320 if message is not None:
321 message = Message.serialize(message)
322
323 self._register_unregister(event_stream_type, True);
324
325 try:
326 packet = Packet.request(command, message)
327 self.transport.send(packet)
328 exited = False
329 while True:
330 response = Packet.parse(self.transport.receive())
331 if response.response_type == Packet.EVENT:
332 if not exited:
333 try:
334 yield Message.deserialize(response.payload)
335 except GeneratorExit:
336 exited = True
337 pass
338 else:
339 break
340
341 if response.response_type == Packet.CMD_RESPONSE:
342 command_response = Message.deserialize(response.payload)
343 else:
344 raise SessionException(
345 "Unexpected response type {type}, "
346 "expected '{response}' (CMD_RESPONSE)".format(
347 type=response.response_type,
348 response=Packet.CMD_RESPONSE
349 )
350 )
351
352 finally:
353 self._register_unregister(event_stream_type, False);
354
355 # evaluate command result, if any
356 if "success" in command_response:
357 if command_response["success"] != b"yes":
358 raise CommandException(
359 "Command failed: {errmsg}".format(
360 errmsg=command_response["errmsg"]
361 )
362 )
363
364 def listen(self, event_types):
365 """Register and listen for the given events.
366
367 :param event_types: event types to register
368 :type event_types: list
369 :return: generator for streamed event responses as (event_type, dict)
370 :rtype: generator
371 """
372 for event_type in event_types:
373 self._register_unregister(event_type, True)
374
375 try:
376 while True:
377 response = Packet.parse(self.transport.receive())
378 if response.response_type == Packet.EVENT:
379 try:
380 yield response.event_type, Message.deserialize(response.payload)
381 except GeneratorExit:
382 break
383
384 finally:
385 for event_type in event_types:
386 self._register_unregister(event_type, False)