From e0f7da8644810d70f2104decc6ca996d8cdb9feb Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 18 Apr 2019 10:56:15 +0200 Subject: [PATCH 1/8] vici: Extract command wrappers in Python bindings This simplifies the interface and allows calling not yet wrapped commands more easily. --- .../vici/python/vici/command_wrappers.py | 206 +++++++++++++++ .../plugins/vici/python/vici/exception.py | 4 + .../plugins/vici/python/vici/session.py | 235 +----------------- 3 files changed, 217 insertions(+), 228 deletions(-) create mode 100644 src/libcharon/plugins/vici/python/vici/command_wrappers.py diff --git a/src/libcharon/plugins/vici/python/vici/command_wrappers.py b/src/libcharon/plugins/vici/python/vici/command_wrappers.py new file mode 100644 index 000000000..75a7e50c4 --- /dev/null +++ b/src/libcharon/plugins/vici/python/vici/command_wrappers.py @@ -0,0 +1,206 @@ +class CommandWrappers(object): + def version(self): + """Retrieve daemon and system specific version information. + + :return: daemon and system specific version information + :rtype: dict + """ + return self.request("version") + + def stats(self): + """Retrieve IKE daemon statistics and load information. + + :return: IKE daemon statistics and load information + :rtype: dict + """ + return self.request("stats") + + def reload_settings(self): + """Reload strongswan.conf settings and any plugins supporting reload. + """ + self.request("reload-settings") + + def initiate(self, sa): + """Initiate an SA. + + :param sa: the SA to initiate + :type sa: dict + :return: generator for logs emitted as dict + :rtype: generator + """ + return self.streamed_request("initiate", "control-log", sa) + + def terminate(self, sa): + """Terminate an SA. + + :param sa: the SA to terminate + :type sa: dict + :return: generator for logs emitted as dict + :rtype: generator + """ + return self.streamed_request("terminate", "control-log", sa) + + def redirect(self, sa): + """Redirect an IKE_SA. + + :param sa: the SA to redirect + :type sa: dict + """ + self.request("redirect", sa) + + def install(self, policy): + """Install a trap, drop or bypass policy defined by a CHILD_SA config. + + :param policy: policy to install + :type policy: dict + """ + self.request("install", policy) + + def uninstall(self, policy): + """Uninstall a trap, drop or bypass policy defined by a CHILD_SA config. + + :param policy: policy to uninstall + :type policy: dict + """ + self.request("uninstall", policy) + + def list_sas(self, filters=None): + """Retrieve active IKE_SAs and associated CHILD_SAs. + + :param filters: retrieve only matching IKE_SAs (optional) + :type filters: dict + :return: generator for active IKE_SAs and associated CHILD_SAs as dict + :rtype: generator + """ + return self.streamed_request("list-sas", "list-sa", filters) + + def list_policies(self, filters=None): + """Retrieve installed trap, drop and bypass policies. + + :param filters: retrieve only matching policies (optional) + :type filters: dict + :return: generator for installed trap, drop and bypass policies as dict + :rtype: generator + """ + return self.streamed_request("list-policies", "list-policy", + filters) + + def list_conns(self, filters=None): + """Retrieve loaded connections. + + :param filters: retrieve only matching configuration names (optional) + :type filters: dict + :return: generator for loaded connections as dict + :rtype: generator + """ + return self.streamed_request("list-conns", "list-conn", + filters) + + def get_conns(self): + """Retrieve connection names loaded exclusively over vici. + + :return: connection names + :rtype: dict + """ + return self.request("get-conns") + + def list_certs(self, filters=None): + """Retrieve loaded certificates. + + :param filters: retrieve only matching certificates (optional) + :type filters: dict + :return: generator for loaded certificates as dict + :rtype: generator + """ + return self.streamed_request("list-certs", "list-cert", filters) + + def load_conn(self, connection): + """Load a connection definition into the daemon. + + :param connection: connection definition + :type connection: dict + """ + self.request("load-conn", connection) + + def unload_conn(self, name): + """Unload a connection definition. + + :param name: connection definition name + :type name: dict + """ + self.request("unload-conn", name) + + def load_cert(self, certificate): + """Load a certificate into the daemon. + + :param certificate: PEM or DER encoded certificate + :type certificate: dict + """ + self.request("load-cert", certificate) + + def load_key(self, private_key): + """Load a private key into the daemon. + + :param private_key: PEM or DER encoded key + """ + self.request("load-key", private_key) + + def load_shared(self, secret): + """Load a shared IKE PSK, EAP or XAuth secret into the daemon. + + :param secret: shared IKE PSK, EAP or XAuth secret + :type secret: dict + """ + self.request("load-shared", secret) + + def flush_certs(self, filter=None): + """Flush the volatile certificate cache. + + Flush the certificate stored temporarily in the cache. The filter + allows to flush only a certain type of certificates, e.g. CRLs. + + :param filter: flush only certificates of a given type (optional) + :type filter: dict + """ + self.request("flush-certs", filter) + + def clear_creds(self): + """Clear credentials loaded over vici. + + Clear all loaded certificate, private key and shared key credentials. + This affects only credentials loaded over vici, but additionally + flushes the credential cache. + """ + self.request("clear-creds") + + def load_pool(self, pool): + """Load a virtual IP pool. + + Load an in-memory virtual IP and configuration attribute pool. + Existing pools with the same name get updated, if possible. + + :param pool: virtual IP and configuration attribute pool + :type pool: dict + """ + return self.request("load-pool", pool) + + def unload_pool(self, pool_name): + """Unload a virtual IP pool. + + Unload a previously loaded virtual IP and configuration attribute pool. + Unloading fails for pools with leases currently online. + + :param pool_name: pool by name + :type pool_name: dict + """ + self.request("unload-pool", pool_name) + + def get_pools(self, options): + """Retrieve loaded pools. + + :param options: filter by name and/or retrieve leases (optional) + :type options: dict + :return: loaded pools + :rtype: dict + """ + return self.request("get-pools", options) diff --git a/src/libcharon/plugins/vici/python/vici/exception.py b/src/libcharon/plugins/vici/python/vici/exception.py index 757ac51a9..9c6606bda 100644 --- a/src/libcharon/plugins/vici/python/vici/exception.py +++ b/src/libcharon/plugins/vici/python/vici/exception.py @@ -1,13 +1,17 @@ """Exception types that may be thrown by this library.""" + class DeserializationException(Exception): """Encountered an unexpected byte sequence or missing element type.""" + class SessionException(Exception): """Session request exception.""" + class CommandException(Exception): """Command result exception.""" + class EventUnknownException(Exception): """Event unknown exception.""" diff --git a/src/libcharon/plugins/vici/python/vici/session.py b/src/libcharon/plugins/vici/python/vici/session.py index 1383fa778..02b6067ba 100644 --- a/src/libcharon/plugins/vici/python/vici/session.py +++ b/src/libcharon/plugins/vici/python/vici/session.py @@ -3,237 +3,15 @@ import socket from .exception import SessionException, CommandException, EventUnknownException from .protocol import Transport, Packet, Message +from .command_wrappers import CommandWrappers -class Session(object): +class Session(CommandWrappers, object): def __init__(self, sock=None): if sock is None: sock = socket.socket(socket.AF_UNIX) sock.connect("/var/run/charon.vici") - self.handler = SessionHandler(Transport(sock)) - - def version(self): - """Retrieve daemon and system specific version information. - - :return: daemon and system specific version information - :rtype: dict - """ - return self.handler.request("version") - - def stats(self): - """Retrieve IKE daemon statistics and load information. - - :return: IKE daemon statistics and load information - :rtype: dict - """ - return self.handler.request("stats") - - def reload_settings(self): - """Reload strongswan.conf settings and any plugins supporting reload. - """ - self.handler.request("reload-settings") - - def initiate(self, sa): - """Initiate an SA. - - :param sa: the SA to initiate - :type sa: dict - :return: generator for logs emitted as dict - :rtype: generator - """ - return self.handler.streamed_request("initiate", "control-log", sa) - - def terminate(self, sa): - """Terminate an SA. - - :param sa: the SA to terminate - :type sa: dict - :return: generator for logs emitted as dict - :rtype: generator - """ - return self.handler.streamed_request("terminate", "control-log", sa) - - def redirect(self, sa): - """Redirect an IKE_SA. - - :param sa: the SA to redirect - :type sa: dict - """ - self.handler.request("redirect", sa) - - def install(self, policy): - """Install a trap, drop or bypass policy defined by a CHILD_SA config. - - :param policy: policy to install - :type policy: dict - """ - self.handler.request("install", policy) - - def uninstall(self, policy): - """Uninstall a trap, drop or bypass policy defined by a CHILD_SA config. - - :param policy: policy to uninstall - :type policy: dict - """ - self.handler.request("uninstall", policy) - - def list_sas(self, filters=None): - """Retrieve active IKE_SAs and associated CHILD_SAs. - - :param filters: retrieve only matching IKE_SAs (optional) - :type filters: dict - :return: generator for active IKE_SAs and associated CHILD_SAs as dict - :rtype: generator - """ - return self.handler.streamed_request("list-sas", "list-sa", filters) - - def list_policies(self, filters=None): - """Retrieve installed trap, drop and bypass policies. - - :param filters: retrieve only matching policies (optional) - :type filters: dict - :return: generator for installed trap, drop and bypass policies as dict - :rtype: generator - """ - return self.handler.streamed_request("list-policies", "list-policy", - filters) - - def list_conns(self, filters=None): - """Retrieve loaded connections. - - :param filters: retrieve only matching configuration names (optional) - :type filters: dict - :return: generator for loaded connections as dict - :rtype: generator - """ - return self.handler.streamed_request("list-conns", "list-conn", - filters) - - def get_conns(self): - """Retrieve connection names loaded exclusively over vici. - - :return: connection names - :rtype: dict - """ - return self.handler.request("get-conns") - - def list_certs(self, filters=None): - """Retrieve loaded certificates. - - :param filters: retrieve only matching certificates (optional) - :type filters: dict - :return: generator for loaded certificates as dict - :rtype: generator - """ - return self.handler.streamed_request("list-certs", "list-cert", filters) - - def load_conn(self, connection): - """Load a connection definition into the daemon. - - :param connection: connection definition - :type connection: dict - """ - self.handler.request("load-conn", connection) - - def unload_conn(self, name): - """Unload a connection definition. - - :param name: connection definition name - :type name: dict - """ - self.handler.request("unload-conn", name) - - def load_cert(self, certificate): - """Load a certificate into the daemon. - - :param certificate: PEM or DER encoded certificate - :type certificate: dict - """ - self.handler.request("load-cert", certificate) - - def load_key(self, private_key): - """Load a private key into the daemon. - - :param private_key: PEM or DER encoded key - """ - self.handler.request("load-key", private_key) - - def load_shared(self, secret): - """Load a shared IKE PSK, EAP or XAuth secret into the daemon. - - :param secret: shared IKE PSK, EAP or XAuth secret - :type secret: dict - """ - self.handler.request("load-shared", secret) - - def flush_certs(self, filter=None): - """Flush the volatile certificate cache. - - Flush the certificate stored temporarily in the cache. The filter - allows to flush only a certain type of certificates, e.g. CRLs. - - :param filter: flush only certificates of a given type (optional) - :type filter: dict - """ - self.handler.request("flush-certs", filter) - - def clear_creds(self): - """Clear credentials loaded over vici. - - Clear all loaded certificate, private key and shared key credentials. - This affects only credentials loaded over vici, but additionally - flushes the credential cache. - """ - self.handler.request("clear-creds") - - def load_pool(self, pool): - """Load a virtual IP pool. - - Load an in-memory virtual IP and configuration attribute pool. - Existing pools with the same name get updated, if possible. - - :param pool: virtual IP and configuration attribute pool - :type pool: dict - """ - return self.handler.request("load-pool", pool) - - def unload_pool(self, pool_name): - """Unload a virtual IP pool. - - Unload a previously loaded virtual IP and configuration attribute pool. - Unloading fails for pools with leases currently online. - - :param pool_name: pool by name - :type pool_name: dict - """ - self.handler.request("unload-pool", pool_name) - - def get_pools(self, options): - """Retrieve loaded pools. - - :param options: filter by name and/or retrieve leases (optional) - :type options: dict - :return: loaded pools - :rtype: dict - """ - return self.handler.request("get-pools", options) - - def listen(self, event_types): - """Register and listen for the given events. - - :param event_types: event types to register - :type event_types: list - :return: generator for streamed event responses as (event_type, dict) - :rtype: generator - """ - return self.handler.listen(event_types) - - -class SessionHandler(object): - """Handles client command execution requests over vici.""" - - def __init__(self, transport): - self.transport = transport + self.transport = Transport(sock) def _communicate(self, packet): """Send packet over transport and parse response. @@ -322,7 +100,7 @@ class SessionHandler(object): if message is not None: message = Message.serialize(message) - self._register_unregister(event_stream_type, True); + self._register_unregister(event_stream_type, True) try: packet = Packet.request(command, message) @@ -352,7 +130,7 @@ class SessionHandler(object): ) finally: - self._register_unregister(event_stream_type, False); + self._register_unregister(event_stream_type, False) # evaluate command result, if any if "success" in command_response: @@ -379,7 +157,8 @@ class SessionHandler(object): response = Packet.parse(self.transport.receive()) if response.response_type == Packet.EVENT: try: - yield response.event_type, Message.deserialize(response.payload) + msg = Message.deserialize(response.payload) + yield response.event_type, msg except GeneratorExit: break From c5113c810505818bad10e49204cba4ac60541488 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 23 Apr 2019 16:13:19 +0200 Subject: [PATCH 2/8] vici: Add missing command wrappers for Python bindings Also change some for which the return value became relevant. --- .../vici/python/vici/command_wrappers.py | 152 +++++++++++++++++- 1 file changed, 150 insertions(+), 2 deletions(-) diff --git a/src/libcharon/plugins/vici/python/vici/command_wrappers.py b/src/libcharon/plugins/vici/python/vici/command_wrappers.py index 75a7e50c4..8a0aa25c5 100644 --- a/src/libcharon/plugins/vici/python/vici/command_wrappers.py +++ b/src/libcharon/plugins/vici/python/vici/command_wrappers.py @@ -40,13 +40,30 @@ class CommandWrappers(object): """ return self.streamed_request("terminate", "control-log", sa) + def rekey(self, sa): + """Initiate the rekeying of an SA. + + .. versionadded:: 5.5.2 + + :param sa: the SA to rekey + :type sa: dict + :return: number of matched SAs + :rtype: dict + """ + return self.request("rekey", sa) + def redirect(self, sa): """Redirect an IKE_SA. + .. versionchanged:: 5.5.2 + The number of matched SAs is returned. + :param sa: the SA to redirect :type sa: dict + :return: number of matched SAs + :rtype: dict """ - self.request("redirect", sa) + return self.request("redirect", sa) def install(self, policy): """Install a trap, drop or bypass policy defined by a CHILD_SA config. @@ -114,6 +131,27 @@ class CommandWrappers(object): """ return self.streamed_request("list-certs", "list-cert", filters) + def list_authorities(self, filters=None): + """Retrieve loaded certification authority information. + + .. versionadded:: 5.3.3 + + :param filters: retrieve only matching CAs (optional) + :type filters: dict + :return: generator for loaded CAs as dict + :rtype: generator + """ + return self.streamed_request("list-authorities", "list-authority", + filters) + + def get_authorities(self): + """Retrieve certification authority names loaded exclusively over vici. + + :return: CA names + :rtype: dict + """ + return self.request("get-authorities") + def load_conn(self, connection): """Load a connection definition into the daemon. @@ -141,18 +179,80 @@ class CommandWrappers(object): def load_key(self, private_key): """Load a private key into the daemon. + .. versionchanged:: 5.5.3 + The key identifier of the loaded key is returned. + :param private_key: PEM or DER encoded key + :type private_key: dict + :return: key identifier + :rtype: dict """ - self.request("load-key", private_key) + return self.request("load-key", private_key) + + def unload_key(self, key_id): + """Unload the private key with the given key identifier. + + .. versionadded:: 5.5.2 + + :param key_id: key identifier + :type key_id: dict + """ + self.request("unload-key", key_id) + + def get_keys(self): + """Retrieve identifiers of private keys loaded exclusively over vici. + + .. versionadded:: 5.5.2 + + :return: key identifiers + :rtype: dict + """ + return self.request("get-keys") + + def load_token(self, token): + """Load a private key located on a token into the daemon. + + .. versionadded:: 5.5.2 + + :param token: token details + :type token: dict + :return: key identifier + :rtype: dict + """ + return self.request("load-token", token) def load_shared(self, secret): """Load a shared IKE PSK, EAP or XAuth secret into the daemon. + .. versionchanged:: 5.5.2 + A unique identifier may be associated with the secret. + :param secret: shared IKE PSK, EAP or XAuth secret :type secret: dict """ self.request("load-shared", secret) + + def unload_shared(self, identifier): + """Unload a previously loaded shared secret by its unique identifier. + + .. versionadded:: 5.5.2 + + :param identifier: unique identifier + :type secret: dict + """ + self.request("unload-shared", identifier) + + def get_shared(self): + """Retrieve identifiers of shared keys loaded exclusively over vici. + + .. versionadded:: 5.5.2 + + :return: identifiers + :rtype: dict + """ + return self.request("get-shared") + def flush_certs(self, filter=None): """Flush the volatile certificate cache. @@ -173,6 +273,22 @@ class CommandWrappers(object): """ self.request("clear-creds") + def load_authority(self, ca): + """Load a certification authority definition into the daemon. + + :param ca: certification authority definition + :type ca: dict + """ + self.request("load-authority", ca) + + def unload_authority(self, ca): + """Unload a previously loaded certification authority by name. + + :param ca: certification authority name + :type ca: dict + """ + self.request("unload-authority", ca) + def load_pool(self, pool): """Load a virtual IP pool. @@ -204,3 +320,35 @@ class CommandWrappers(object): :rtype: dict """ return self.request("get-pools", options) + + def get_algorithms(self): + """List of currently loaded algorithms and their implementation. + + .. versionadded:: 5.4.0 + + :return: algorithms + :rtype: dict + """ + return self.request("get-algorithms") + + def get_counters(self, options=None): + """List global or connection-specific counters for several IKE events. + + .. versionadded:: 5.6.1 + + :param options: get global counters or those of all or one connection + :type options: dict + :return: counters + :rtype: dict + """ + return self.request("get-counters", options) + + def reset_counters(self, options=None): + """Reset global or connection-specific IKE event counters. + + .. versionadded:: 5.6.1 + + :param options: reset global counters or those of all or one connection + :type options: dict + """ + self.request("reset-counters", options) From 42fe703a952f5ab0213516d89e192d373c9ccc11 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Tue, 23 Apr 2019 19:56:22 +0200 Subject: [PATCH 3/8] vici: Fix formatting of return values for load-conn and load-authority commands --- src/libcharon/plugins/vici/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libcharon/plugins/vici/README.md b/src/libcharon/plugins/vici/README.md index f029d06d7..a0ab5608a 100644 --- a/src/libcharon/plugins/vici/README.md +++ b/src/libcharon/plugins/vici/README.md @@ -448,10 +448,10 @@ with the same name gets updated or replaced. = { # IKE configuration parameters with authentication and CHILD_SA # subsections. Refer to swanctl.conf(5) for details. - } => { - success = - errmsg = } + } => { + success = + errmsg = } ### unload-conn() ### @@ -603,10 +603,10 @@ authority with the same name gets replaced. = { # certification authority parameters # refer to swanctl.conf(5) for details. - } => { - success = - errmsg = } + } => { + success = + errmsg = } ### unload-authority() ### From 3b3944455665d8ad3bd348a1dcb43c8ce4be3566 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 24 Apr 2019 16:05:12 +0200 Subject: [PATCH 4/8] vici: Refactor how commands are called in the Ruby bindings Also expose a method to call arbitrary commands, which allows calling not yet wrapped commands. Exceptions are raised for all commands if the response includes a negative "success" key (similar to how it's done in the Python bindings). --- src/libcharon/plugins/vici/ruby/lib/vici.rb | 55 ++++++++++++--------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/src/libcharon/plugins/vici/ruby/lib/vici.rb b/src/libcharon/plugins/vici/ruby/lib/vici.rb index 61de99a1f..8dd436612 100644 --- a/src/libcharon/plugins/vici/ruby/lib/vici.rb +++ b/src/libcharon/plugins/vici/ruby/lib/vici.rb @@ -3,6 +3,9 @@ # strongSwan VICI protocol. The Connection class provides a high-level # interface to issue requests or listen for events. # +# Copyright (C) 2019 Tobias Brunner +# HSR Hochschule fuer Technik Rapperswil +# # Copyright (C) 2014 Martin Willi # Copyright (C) 2014 revosec AG # @@ -25,7 +28,6 @@ # THE SOFTWARE. module Vici - ## # Vici specific exception all others inherit from class Error < StandardError @@ -433,117 +435,115 @@ module Vici ## # Load a connection into the daemon. def load_conn(conn) - check_success(@transp.request("load-conn", Message.new(conn))) + call("load-conn", Message.new(conn)) end ## # Unload a connection from the daemon. def unload_conn(conn) - check_success(@transp.request("unload-conn", Message.new(conn))) + call("unload-conn", Message.new(conn)) end ## # Get the names of connections managed by vici. def get_conns() - @transp.request("get-conns").root + call("get-conns") end ## # Flush credential cache. def flush_certs(match = nil) - check_success(@transp.request("flush-certs", Message.new(match))) + call("flush-certs", Message.new(match)) end ## # Clear all loaded credentials. def clear_creds() - check_success(@transp.request("clear-creds")) + call("clear-creds") end ## # Load a certificate into the daemon. def load_cert(cert) - check_success(@transp.request("load-cert", Message.new(cert))) + call("load-cert", Message.new(cert)) end ## # Load a private key into the daemon. def load_key(key) - check_success(@transp.request("load-key", Message.new(key))) + call("load-key", Message.new(key)) end ## # Load a shared key into the daemon. def load_shared(shared) - check_success(@transp.request("load-shared", Message.new(shared))) + call("load-shared", Message.new(shared)) end ## # Load a virtual IP / attribute pool def load_pool(pool) - check_success(@transp.request("load-pool", Message.new(pool))) + call("load-pool", Message.new(pool)) end ## # Unload a virtual IP / attribute pool def unload_pool(pool) - check_success(@transp.request("unload-pool", Message.new(pool))) + call("unload-pool", Message.new(pool)) end ## # Get the currently loaded pools. def get_pools(options) - @transp.request("get-pools", Message.new(options)).root + call("get-pools", Message.new(options)) end ## # Initiate a connection. The provided closure is invoked for each log line. def initiate(options, &block) - check_success(call_with_event("initiate", Message.new(options), - "control-log", &block)) + call_with_event("initiate", Message.new(options), "control-log", &block) end ## # Terminate a connection. The provided closure is invoked for each log line. def terminate(options, &block) - check_success(call_with_event("terminate", Message.new(options), - "control-log", &block)) + call_with_event("terminate", Message.new(options), "control-log", &block) end ## # Redirect an IKE_SA. def redirect(options) - check_success(@transp.request("redirect", Message.new(options))) + call("redirect", Message.new(options)) end ## # Install a shunt/route policy. def install(policy) - check_success(@transp.request("install", Message.new(policy))) + call("install", Message.new(policy)) end ## # Uninstall a shunt/route policy. def uninstall(policy) - check_success(@transp.request("uninstall", Message.new(policy))) + call("uninstall", Message.new(policy)) end ## # Reload strongswan.conf settings. def reload_settings - check_success(@transp.request("reload-settings", nil)) + call("reload-settings") end ## # Get daemon statistics and information. def stats - @transp.request("stats", nil).root + call("stats") end ## # Get daemon version information def version - @transp.request("version", nil).root + call("version") end ## @@ -573,6 +573,13 @@ module Vici end end + ## + # Issue a command request. Checks if the reply of a command indicates + # "success", otherwise raises a CommandExecError exception. + def call(command, request = nil) + check_success(@transp.request(command, request)) + end + ## # Issue a command request, but register for a specific event while the # command is active. VICI uses this mechanism to stream potentially large @@ -590,7 +597,7 @@ module Vici ensure @transp.unregister(event, method(:call_event)) end - reply + check_success(reply) end ## @@ -598,7 +605,7 @@ module Vici # CommandExecError exception def check_success(reply) root = reply.root - if root["success"] != "yes" + if root.key?("success") && root["success"] != "yes" raise CommandExecError, root["errmsg"] end root From 1fef01af58e17d69b05bd337455c89843c35d4ef Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 24 Apr 2019 16:37:11 +0200 Subject: [PATCH 5/8] vici: Update command wrappers of the Ruby bindings Also reorder them to match README.md. --- src/libcharon/plugins/vici/ruby/lib/vici.rb | 270 +++++++++++++------- 1 file changed, 175 insertions(+), 95 deletions(-) diff --git a/src/libcharon/plugins/vici/ruby/lib/vici.rb b/src/libcharon/plugins/vici/ruby/lib/vici.rb index 8dd436612..0fd4b37bb 100644 --- a/src/libcharon/plugins/vici/ruby/lib/vici.rb +++ b/src/libcharon/plugins/vici/ruby/lib/vici.rb @@ -404,98 +404,21 @@ module Vici end ## - # List matching loaded connections. The provided closure is invoked - # for each matching connection. - def list_conns(match = nil, &block) - call_with_event("list-conns", Message.new(match), "list-conn", &block) + # Get daemon version information + def version + call("version") end ## - # List matching active SAs. The provided closure is invoked for each - # matching SA. - def list_sas(match = nil, &block) - call_with_event("list-sas", Message.new(match), "list-sa", &block) + # Get daemon statistics and information. + def stats + call("stats") end ## - # List matching installed policies. The provided closure is invoked - # for each matching policy. - def list_policies(match, &block) - call_with_event("list-policies", Message.new(match), "list-policy", - &block) - end - - ## - # List matching loaded certificates. The provided closure is invoked - # for each matching certificate definition. - def list_certs(match = nil, &block) - call_with_event("list-certs", Message.new(match), "list-cert", &block) - end - - ## - # Load a connection into the daemon. - def load_conn(conn) - call("load-conn", Message.new(conn)) - end - - ## - # Unload a connection from the daemon. - def unload_conn(conn) - call("unload-conn", Message.new(conn)) - end - - ## - # Get the names of connections managed by vici. - def get_conns() - call("get-conns") - end - - ## - # Flush credential cache. - def flush_certs(match = nil) - call("flush-certs", Message.new(match)) - end - - ## - # Clear all loaded credentials. - def clear_creds() - call("clear-creds") - end - - ## - # Load a certificate into the daemon. - def load_cert(cert) - call("load-cert", Message.new(cert)) - end - - ## - # Load a private key into the daemon. - def load_key(key) - call("load-key", Message.new(key)) - end - - ## - # Load a shared key into the daemon. - def load_shared(shared) - call("load-shared", Message.new(shared)) - end - - ## - # Load a virtual IP / attribute pool - def load_pool(pool) - call("load-pool", Message.new(pool)) - end - - ## - # Unload a virtual IP / attribute pool - def unload_pool(pool) - call("unload-pool", Message.new(pool)) - end - - ## - # Get the currently loaded pools. - def get_pools(options) - call("get-pools", Message.new(options)) + # Reload strongswan.conf settings. + def reload_settings + call("reload-settings") end ## @@ -510,6 +433,12 @@ module Vici call_with_event("terminate", Message.new(options), "control-log", &block) end + ## + # Initiate the rekeying of an SA. + def rekey(options) + call("rekey", Message.new(options)) + end + ## # Redirect an IKE_SA. def redirect(options) @@ -529,21 +458,172 @@ module Vici end ## - # Reload strongswan.conf settings. - def reload_settings - call("reload-settings") + # List matching active SAs. The provided closure is invoked for each + # matching SA. + def list_sas(match = nil, &block) + call_with_event("list-sas", Message.new(match), "list-sa", &block) end ## - # Get daemon statistics and information. - def stats - call("stats") + # List matching installed policies. The provided closure is invoked + # for each matching policy. + def list_policies(match, &block) + call_with_event("list-policies", Message.new(match), "list-policy", + &block) end ## - # Get daemon version information - def version - call("version") + # List matching loaded connections. The provided closure is invoked + # for each matching connection. + def list_conns(match = nil, &block) + call_with_event("list-conns", Message.new(match), "list-conn", &block) + end + + ## + # Get the names of connections managed by vici. + def get_conns() + call("get-conns") + end + + ## + # List matching loaded certificates. The provided closure is invoked + # for each matching certificate definition. + def list_certs(match = nil, &block) + call_with_event("list-certs", Message.new(match), "list-cert", &block) + end + + ## + # List matching loaded certification authorities. The provided closure is + # invoked for each matching certification authority definition. + def list_authorities(match = nil, &block) + call_with_event("list-authorities", Message.new(match), "list-authority", + &block) + end + + ## + # Get the names of certification authorities managed by vici. + def get_authorities() + call("get-authorities") + end + + ## + # Load a connection into the daemon. + def load_conn(conn) + call("load-conn", Message.new(conn)) + end + + ## + # Unload a connection from the daemon. + def unload_conn(conn) + call("unload-conn", Message.new(conn)) + end + + ## + # Load a certificate into the daemon. + def load_cert(cert) + call("load-cert", Message.new(cert)) + end + + ## + # Load a private key into the daemon. + def load_key(key) + call("load-key", Message.new(key)) + end + + ## + # Unload a private key from the daemon. + def unload_key(key) + call("unload-key", Message.new(key)) + end + + ## + # Get the identifiers of private keys loaded via vici. + def get_keys() + call("get-keys") + end + + ## + # Load a private key located on a token into the daemon. + def load_token(token) + call("load-token", Message.new(token)) + end + + ## + # Load a shared key into the daemon. + def load_shared(shared) + call("load-shared", Message.new(shared)) + end + + ## + # Unload a shared key from the daemon. + def unload_shared(shared) + call("unload-shared", Message.new(shared)) + end + + ## + # Get the unique identifiers of shared keys loaded via vici. + def get_shared() + call("get-shared") + end + + ## + # Flush credential cache. + def flush_certs(match = nil) + call("flush-certs", Message.new(match)) + end + + ## + # Clear all loaded credentials. + def clear_creds() + call("clear-creds") + end + + ## + # Load a certification authority into the daemon. + def load_authority(authority) + call("load-authority", Message.new(authority)) + end + + ## + # Unload a certification authority from the daemon. + def unload_authority(authority) + call("unload-authority", Message.new(authority)) + end + + ## + # Load a virtual IP / attribute pool into the daemon. + def load_pool(pool) + call("load-pool", Message.new(pool)) + end + + ## + # Unload a virtual IP / attribute pool from the daemon. + def unload_pool(pool) + call("unload-pool", Message.new(pool)) + end + + ## + # Get the currently loaded pools. + def get_pools(options) + call("get-pools", Message.new(options)) + end + + ## + # Get currently loaded algorithms and their implementation. + def get_algorithms() + call("get-algorithms") + end + + ## + # Get global or connection-specific counters for IKE events. + def get_counters(options = nil) + call("get-counters", Message.new(options)) + end + + ## + # Reset global or connection-specific IKE event counters. + def reset_counters(options = nil) + call("reset-counters", Message.new(options)) end ## From cc2ef8f8a772693e4d2fea63c9ad8375d0830a5a Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Wed, 24 Apr 2019 18:05:11 +0200 Subject: [PATCH 6/8] vici: Some code style fixes in the Ruby bindings As reported by rubocop (some issues were not fixed, in particular related to class/method length metrics). --- src/libcharon/plugins/vici/ruby/.rubocop.yml | 5 + src/libcharon/plugins/vici/ruby/lib/vici.rb | 231 ++++++++----------- 2 files changed, 105 insertions(+), 131 deletions(-) create mode 100644 src/libcharon/plugins/vici/ruby/.rubocop.yml diff --git a/src/libcharon/plugins/vici/ruby/.rubocop.yml b/src/libcharon/plugins/vici/ruby/.rubocop.yml new file mode 100644 index 000000000..9348ff926 --- /dev/null +++ b/src/libcharon/plugins/vici/ruby/.rubocop.yml @@ -0,0 +1,5 @@ +Naming/AccessorMethodName: + Enabled: false + +Style/StringLiterals: + EnforcedStyle: double_quotes diff --git a/src/libcharon/plugins/vici/ruby/lib/vici.rb b/src/libcharon/plugins/vici/ruby/lib/vici.rb index 0fd4b37bb..23596850e 100644 --- a/src/libcharon/plugins/vici/ruby/lib/vici.rb +++ b/src/libcharon/plugins/vici/ruby/lib/vici.rb @@ -78,12 +78,10 @@ module Vici class StopEventListening < Exception end - ## # The Message class provides the low level encoding and decoding of vici # protocol messages. Directly using this class is usually not required. class Message - SECTION_START = 1 SECTION_END = 2 KEY_VALUE = 3 @@ -92,8 +90,8 @@ module Vici LIST_END = 6 def initialize(data = "") - if data == nil - @root = Hash.new() + if data.nil? + @root = {} elsif data.is_a?(Hash) @root = data else @@ -104,18 +102,14 @@ module Vici ## # Get the raw byte encoding of an on-the-wire message def encoding - if @encoded == nil - @encoded = encode(@root) - end + @encoded = encode(@root) if @encoded.nil? @encoded end ## # Get the root element of the parsed ruby data structures def root - if @root == nil - @root = parse(@encoded) - end + @root = parse(@encoded) if @root.nil? @root end @@ -126,9 +120,7 @@ module Vici end def encode_value(value) - if value.class != String - value = value.to_s - end + value = value.to_s if value.class != String [value.length].pack("n") << value end @@ -152,18 +144,13 @@ module Vici def encode(node) encoding = "" node.each do |key, value| - case value.class - when String, Fixnum, true, false - encoding = encode_kv(encoding, key, value) - else - if value.is_a?(Hash) - encoding = encode_section(encoding, key, value) - elsif value.is_a?(Array) - encoding = encode_list(encoding, key, value) - else - encoding = encode_kv(encoding, key, value) - end - end + encoding = if value.is_a?(Hash) + encode_section(encoding, key, value) + elsif value.is_a?(Array) + encode_list(encoding, key, value) + else + encode_kv(encoding, key, value) + end end encoding end @@ -171,63 +158,57 @@ module Vici def parse_name(encoding) len = encoding.unpack("c")[0] name = encoding[1, len] - return encoding[(1 + len)..-1], name + [encoding[(1 + len)..-1], name] end def parse_value(encoding) len = encoding.unpack("n")[0] value = encoding[2, len] - return encoding[(2 + len)..-1], value + [encoding[(2 + len)..-1], value] end def parse(encoding) - stack = [Hash.new] + stack = [{}] list = nil - while encoding.length != 0 do + until encoding.empty? type = encoding.unpack("c")[0] encoding = encoding[1..-1] case type - when SECTION_START - encoding, name = parse_name(encoding) - stack.push(stack[-1][name] = Hash.new) - when SECTION_END - if stack.length() == 1 - raise ParseError, "unexpected section end" - end - stack.pop() - when KEY_VALUE - encoding, name = parse_name(encoding) - encoding, value = parse_value(encoding) - stack[-1][name] = value - when LIST_START - encoding, name = parse_name(encoding) - stack[-1][name] = [] - list = name - when LIST_ITEM - raise ParseError, "unexpected list item" if list == nil - encoding, value = parse_value(encoding) - stack[-1][list].push(value) - when LIST_END - raise ParseError, "unexpected list end" if list == nil - list = nil - else - raise ParseError, "invalid type: #{type}" + when SECTION_START + encoding, name = parse_name(encoding) + stack.push(stack[-1][name] = {}) + when SECTION_END + raise ParseError, "unexpected section end" if stack.length == 1 + stack.pop + when KEY_VALUE + encoding, name = parse_name(encoding) + encoding, value = parse_value(encoding) + stack[-1][name] = value + when LIST_START + encoding, name = parse_name(encoding) + stack[-1][name] = [] + list = name + when LIST_ITEM + raise ParseError, "unexpected list item" if list.nil? + encoding, value = parse_value(encoding) + stack[-1][list].push(value) + when LIST_END + raise ParseError, "unexpected list end" if list.nil? + list = nil + else + raise ParseError, "invalid type: #{type}" end end - if stack.length() > 1 - raise ParseError, "unexpected message end" - end + raise ParseError, "unexpected message end" if stack.length > 1 stack[0] end end - ## # The Transport class implements to low level segmentation of packets # to the underlying transport stream. Directly using this class is usually # not required. class Transport - CMD_REQUEST = 0 CMD_RESPONSE = 1 CMD_UNKNOWN = 2 @@ -241,18 +222,16 @@ module Vici # Create a transport layer using a provided socket for communication. def initialize(socket) @socket = socket - @events = Hash.new + @events = {} end ## # Receive data from socket, until len bytes read def recv_all(len) encoding = "" - while encoding.length < len do + while encoding.length < len data = @socket.recv(len - encoding.length) - if data.empty? - raise TransportError, "connection closed" - end + raise TransportError, "connection closed" if data.empty? encoding << data end encoding @@ -262,9 +241,7 @@ module Vici # Send data to socket, until all bytes sent def send_all(encoding) len = 0 - while len < encoding.length do - len += @socket.send(encoding[len..-1], 0) - end + len += @socket.send(encoding[len..-1], 0) while len < encoding.length end ## @@ -272,12 +249,8 @@ module Vici # specifies the message, the optional label and message get appended. def write(type, label, message) encoding = "" - if label - encoding << label.length << label - end - if message - encoding << message.encoding - end + encoding << label.length << label if label + encoding << message.encoding if message send_all([encoding.length + 1, type].pack("Nc") + encoding) end @@ -290,18 +263,20 @@ module Vici type = encoding.unpack("c")[0] len = 1 case type - when CMD_REQUEST, EVENT_REGISTER, EVENT_UNREGISTER, EVENT - label = encoding[2, encoding[1].unpack("c")[0]] - len += label.length + 1 - when CMD_RESPONSE, CMD_UNKNOWN, EVENT_CONFIRM, EVENT_UNKNOWN - label = nil - else - raise TransportError, "invalid message: #{type}" + when CMD_REQUEST, EVENT_REGISTER, EVENT_UNREGISTER, EVENT + label = encoding[2, encoding[1].unpack("c")[0]] + len += label.length + 1 + when CMD_RESPONSE, CMD_UNKNOWN, EVENT_CONFIRM, EVENT_UNKNOWN + label = nil + else + raise TransportError, "invalid message: #{type}" end - if encoding.length == len - return type, label, Message.new - end - return type, label, Message.new(encoding[len..-1]) + message = if encoding.length == len + Message.new + else + Message.new(encoding[len..-1]) + end + [type, label, message] end def dispatch_event(name, message) @@ -312,22 +287,17 @@ module Vici def read_and_dispatch_event type, label, message = read - p - if type == EVENT - dispatch_event(label, message) - else - raise TransportError, "unexpected message: #{type}" - end + raise TransportError, "unexpected message: #{type}" if type != EVENT + + dispatch_event(label, message) end def read_and_dispatch_events loop do type, label, message = read - if type == EVENT - dispatch_event(label, message) - else - return type, label, message - end + return type, label, message if type != EVENT + + dispatch_event(label, message) end end @@ -336,14 +306,14 @@ module Vici # the reply message on success. def request(name, message = nil) write(CMD_REQUEST, name, message) - type, label, message = read_and_dispatch_events + type, _label, message = read_and_dispatch_events case type - when CMD_RESPONSE - return message - when CMD_UNKNOWN - raise CommandUnknownError, name - else - raise CommandError, "invalid response for #{name}" + when CMD_RESPONSE + return message + when CMD_UNKNOWN + raise CommandUnknownError, name + else + raise CommandError, "invalid response for #{name}" end end @@ -351,18 +321,18 @@ module Vici # Register a handler method for the given event name def register(name, handler) write(EVENT_REGISTER, name, nil) - type, label, message = read_and_dispatch_events + type, _label, _message = read_and_dispatch_events case type - when EVENT_CONFIRM - if @events.has_key?(name) - @events[name] += [handler] - else - @events[name] = [handler]; - end - when EVENT_UNKNOWN - raise EventUnknownError, name + when EVENT_CONFIRM + if @events.key?(name) + @events[name] += [handler] else - raise EventError, "invalid response for #{name} register" + @events[name] = [handler] + end + when EVENT_UNKNOWN + raise EventUnknownError, name + else + raise EventError, "invalid response for #{name} register" end end @@ -370,19 +340,18 @@ module Vici # Unregister a handler method for the given event name def unregister(name, handler) write(EVENT_UNREGISTER, name, nil) - type, label, message = read_and_dispatch_events + type, _label, _message = read_and_dispatch_events case type - when EVENT_CONFIRM - @events[name] -= [handler] - when EVENT_UNKNOWN - raise EventUnknownError, name - else - raise EventError, "invalid response for #{name} unregister" + when EVENT_CONFIRM + @events[name] -= [handler] + when EVENT_UNKNOWN + raise EventUnknownError, name + else + raise EventError, "invalid response for #{name} unregister" end end end - ## # The Connection class provides the high-level interface to monitor, configure # and control the IKE daemon. It takes a connected stream-oriented Socket for @@ -395,11 +364,10 @@ module Vici # Non-String values that are not a Hash nor an Array get converted with .to_s # during encoding. class Connection - + ## + # Create a connection, optionally using the given socket def initialize(socket = nil) - if socket == nil - socket = UNIXSocket.new("/var/run/charon.vici") - end + socket = UNIXSocket.new("/var/run/charon.vici") if socket.nil? @transp = Transport.new(socket) end @@ -481,7 +449,7 @@ module Vici ## # Get the names of connections managed by vici. - def get_conns() + def get_conns call("get-conns") end @@ -502,7 +470,7 @@ module Vici ## # Get the names of certification authorities managed by vici. - def get_authorities() + def get_authorities call("get-authorities") end @@ -538,7 +506,7 @@ module Vici ## # Get the identifiers of private keys loaded via vici. - def get_keys() + def get_keys call("get-keys") end @@ -562,7 +530,7 @@ module Vici ## # Get the unique identifiers of shared keys loaded via vici. - def get_shared() + def get_shared call("get-shared") end @@ -574,7 +542,7 @@ module Vici ## # Clear all loaded credentials. - def clear_creds() + def clear_creds call("clear-creds") end @@ -610,7 +578,7 @@ module Vici ## # Get currently loaded algorithms and their implementation. - def get_algorithms() + def get_algorithms call("get-algorithms") end @@ -667,7 +635,7 @@ module Vici # event messages. def call_with_event(command, request, event, &block) self.class.instance_eval do - define_method(:call_event) do |label, message| + define_method(:call_event) do |_label, message| block.call(message.root) end end @@ -688,6 +656,7 @@ module Vici if root.key?("success") && root["success"] != "yes" raise CommandExecError, root["errmsg"] end + root end end From 968866afc6710cb1e45aa4fda72b3e4677b8148b Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 25 Apr 2019 10:26:11 +0200 Subject: [PATCH 7/8] vici: Update some data in the Ruby gemspec --- src/libcharon/plugins/vici/ruby/vici.gemspec.in | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libcharon/plugins/vici/ruby/vici.gemspec.in b/src/libcharon/plugins/vici/ruby/vici.gemspec.in index 2bd2b3d88..7a1e8ee56 100644 --- a/src/libcharon/plugins/vici/ruby/vici.gemspec.in +++ b/src/libcharon/plugins/vici/ruby/vici.gemspec.in @@ -1,15 +1,15 @@ Gem::Specification.new do |s| s.name = "vici" s.version = "@GEM_VERSION@" - s.authors = ["Martin Willi"] - s.email = ["martin@strongswan.org"] + s.authors = ["strongSwan Project"] + s.email = ["info@strongswan.org"] s.description = %q{ The strongSwan VICI protocol allows external application to monitor, - configure and control the IKE daemon charon. This ruby gem provides a + configure and control the IKE daemon charon. This Ruby Gem provides a native client side implementation of the VICI protocol, well suited to script automated tasks in a relaible way. } - s.summary = "Native ruby interface for strongSwan VICI" + s.summary = "Native Ruby interface for strongSwan VICI" s.homepage = "https://wiki.strongswan.org/projects/strongswan/wiki/Vici" s.license = "MIT" s.files = "lib/vici.rb" From eefa81120cd8ad06059a74d219882060f249c107 Mon Sep 17 00:00:00 2001 From: Tobias Brunner Date: Thu, 25 Apr 2019 11:09:20 +0200 Subject: [PATCH 8/8] vici: Update command wrappers in the Perl bindings Note that load_key() now returns the complete response (to get the key identifier). --- .../perl/Vici-Session/lib/Vici/Session.pm | 39 ++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm b/src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm index 5c09b14ed..27c023e52 100644 --- a/src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm +++ b/src/libcharon/plugins/vici/perl/Vici-Session/lib/Vici/Session.pm @@ -36,6 +36,10 @@ sub terminate { return request_vars_res('terminate', @_); } +sub rekey { + return request_vars_res('rekey', @_); +} + sub redirect { return request_vars_res('redirect', @_); } @@ -89,13 +93,33 @@ sub load_cert { } sub load_key { - return request_vars_res('load-key', @_); + return request_vars('load-key', @_); +} + +sub unload_key { + return request_vars_res('unload-key', @_); +} + +sub get_keys { + return request('get-keys', @_); +} + +sub load_token { + return request_vars('load-token', @_); } sub load_shared { return request_vars_res('load-shared', @_); } +sub unload_shared { + return request_vars_res('unload-shared', @_); +} + +sub get_shared { + return request('get-shared', @_); +} + sub flush_certs { return request_vars_res('flush-certs', @_); } @@ -128,6 +152,14 @@ sub get_algorithms { return request('get-algorithms', @_); } +sub get_counters { + return request_vars('get-counters', @_); +} + +sub reset_counters { + return request_vars_res('reset-counters', @_); +} + # Private functions sub request { @@ -135,6 +167,11 @@ sub request { return $self->{'Packet'}->request($command); } +sub request_vars { + my ($command, $self, $vars) = @_; + return $self->{'Packet'}->request($command, $vars); +} + sub request_res { my ($command, $self) = @_; my $msg = $self->{'Packet'}->request($command);