Re: Caching VPN secrets



On Tue, 2014-08-19 at 11:06 -0500, David Woodhouse wrote:
Sitting on a crappy hotel network this week, I'm annoyed by how often I
have to reauthenticate to my VPN server. I'd *really* like the secrets
to be cached so that it can just *work* when I fall off the wireless and
rejoin.

It doesn't look *so* hard to do this with a hack in
nm-openconnect-service itself. When we connect, remember the secrets
locally (and disable the "quit" signal so we don't exit on idle).

If we later get a need_secrets() call, and if we already have them
locally (although they won't be in the settings dictionary that we're
sent), then just return FALSE. We'll get a connect() call, and we can
use the cached secrets to make the connection. If the connection *fails*
then invalidate the cached secrets.

That kind of works, but it's ugly. And at the very least, we'd really
like to differentiate between a disconnect call which happens because
the physical network went down, and a disconnect call triggered by
explicit user request. We'd probably want to sign off and terminate (and
forget) the session on the latter, but keep it alive and cached in the
former case.

But really, rather than hacking this up in the vpn service itself,
perhaps what I want here is a new secret flag called something like
NM_SETTING_SECRET_SAVED_NON_PERSISTENT — which allows the secret to be
stored, but only in *memory* for the lifetime of a given session. And
then they could be automatically discarded when the user explicitly
requests a disconnect, etc.

Thoughts?

(Full citation preserved since I'm not sure the original got through.
Yay for crappy hotel networks)

Here's my hack, now that it's actually working:

diff --git a/src/nm-openconnect-service-openconnect-helper.c b/src/nm-openconnect-service-openconnect-helper.c
index 2fa6852..7ad5e75 100644
--- a/src/nm-openconnect-service-openconnect-helper.c
+++ b/src/nm-openconnect-service-openconnect-helper.c
@@ -620,6 +620,19 @@ main (int argc, char *argv[])
        if (val)
                g_hash_table_insert (ip6config, NM_VPN_PLUGIN_IP6_CONFIG_DNS, val);
 
+       /* Session timeout */
+       tmp = getenv ("CISCO_CSTP_OPTIONS");
+       while (tmp) {
+               if (!strncmp(tmp, "X-CSTP-Session-Timeout=", 23)) {
+                       val = uint_to_gvalue (strtol (tmp + 23, NULL, 10));
+                       g_hash_table_insert(config, "openconnect_session_timeout", val);
+                       break;
+               }
+               tmp = strchr(tmp, '\n');
+               if (tmp)
+                       tmp++;
+       }
+
        /* Routes */
        val = get_ip6_routes ();
        if (val) {
diff --git a/src/nm-openconnect-service.c b/src/nm-openconnect-service.c
index 7dcf964..d23640d 100644
--- a/src/nm-openconnect-service.c
+++ b/src/nm-openconnect-service.c
@@ -54,6 +54,12 @@
 G_DEFINE_TYPE (NMOPENCONNECTPlugin, nm_openconnect_plugin, NM_TYPE_VPN_PLUGIN)
 
 typedef struct {
+       char *uuid;
+       char *cookie;
+       char *gateway;
+       char *gwcert;
+       time_t cookie_start;
+       unsigned long cookie_validity;
        GPid pid;
        char *tun_name;
 } NMOPENCONNECTPluginPrivate;
@@ -103,6 +109,7 @@ static ValidProperty valid_secrets[] = {
        { NULL,                       G_TYPE_NONE, 0, 0 }
 };
 
+
 static uid_t tun_owner;
 static gid_t tun_group;
 static gboolean debug = FALSE;
@@ -114,6 +121,7 @@ typedef struct ValidateInfo {
        gboolean have_items;
 } ValidateInfo;
 
+
 static void
 validate_one_property (const char *key, const char *value, gpointer user_data)
 {
@@ -387,6 +395,8 @@ nm_openconnect_start_openconnect_binary (NMOPENCONNECTPlugin *plugin,
        /* The actual gateway to use (after redirection) comes from the auth
           dialog, so it's in the secrets hash not the properties */
        props_vpn_gw = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GATEWAY);
+       if (!props_vpn_gw || !strlen (props_vpn_gw) )
+               props_vpn_gw = priv->gateway;
        if (!props_vpn_gw || !strlen (props_vpn_gw) ) {
                g_set_error (error,
                             NM_VPN_PLUGIN_ERROR,
@@ -397,6 +407,8 @@ nm_openconnect_start_openconnect_binary (NMOPENCONNECTPlugin *plugin,
        }
 
        props_cookie = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_COOKIE);
+       if (!props_cookie || !strlen (props_cookie) )
+               props_cookie = priv->cookie;
        if (!props_cookie || !strlen (props_cookie)) {
                g_set_error (error,
                             NM_VPN_PLUGIN_ERROR,
@@ -406,6 +418,8 @@ nm_openconnect_start_openconnect_binary (NMOPENCONNECTPlugin *plugin,
                return -1;
        }
        props_gwcert = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GWCERT);
+       if (!props_gwcert || !strlen (props_gwcert) )
+               props_gwcert = priv->gwcert;
 
        props_cacert = nm_setting_vpn_get_data_item (s_vpn, NM_OPENCONNECT_KEY_CACERT);
        props_mtu = nm_setting_vpn_get_data_item (s_vpn, NM_OPENCONNECT_KEY_MTU);
@@ -452,6 +466,9 @@ nm_openconnect_start_openconnect_binary (NMOPENCONNECTPlugin *plugin,
 
        g_ptr_array_add (openconnect_argv, NULL);
 
+       /* Only allow the cookie reuse when connections are successful */
+       priv->cookie_validity = 0;
+
        if (!g_spawn_async_with_pipes (NULL, (char **) openconnect_argv->pdata, NULL,
                                       G_SPAWN_DO_NOT_REAP_CHILD,
                                       openconnect_drop_child_privs, priv->tun_name,
@@ -507,16 +524,34 @@ real_connect (NMVPNPlugin   *plugin,
 }
 
 static gboolean
+cached_secrets_valid (NMVPNPlugin *plugin, NMSettingVPN *s_vpn, const char *uuid)
+{
+       NMOPENCONNECTPluginPrivate *priv = NM_OPENCONNECT_PLUGIN_GET_PRIVATE (plugin);
+
+       if (!uuid || !priv->uuid || !priv->cookie_validity || strcmp(priv->uuid, uuid))
+               return FALSE;
+
+       /* Only use cached cookie if it's valid for at least an hour */
+       if ((priv->cookie_start + priv->cookie_validity) > time(NULL) + 3600)
+               return TRUE;
+
+       return FALSE;
+}
+
+static gboolean
 real_need_secrets (NMVPNPlugin *plugin,
                    NMConnection *connection,
                    char **setting_name,
                    GError **error)
 {
+       NMOPENCONNECTPluginPrivate *priv = NM_OPENCONNECT_PLUGIN_GET_PRIVATE (plugin);
        NMSettingVPN *s_vpn;
+       const char *gateway, *cookie, *gwcert, *uuid;
 
        g_return_val_if_fail (NM_IS_VPN_PLUGIN (plugin), FALSE);
        g_return_val_if_fail (NM_IS_CONNECTION (connection), FALSE);
 
+       uuid = nm_connection_get_uuid (connection);
        s_vpn = NM_SETTING_VPN (nm_connection_get_setting (connection, NM_TYPE_SETTING_VPN));
        if (!s_vpn) {
                g_set_error (error,
@@ -530,18 +565,26 @@ real_need_secrets (NMVPNPlugin *plugin,
        /* We just need the WebVPN cookie, and the final IP address of the gateway
           (after HTTP redirects, which do happen). All the certificate/SecurID 
           nonsense can be handled for us, in the user's context, by auth-dialog */
-       if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GATEWAY)) {
-               *setting_name = NM_SETTING_VPN_SETTING_NAME;
-               return TRUE;
-       }
-       if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_COOKIE)) {
-               *setting_name = NM_SETTING_VPN_SETTING_NAME;
-               return TRUE;
-       }
-       if (!nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GWCERT)) {
+       gateway = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GATEWAY);
+       cookie = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_COOKIE);
+       gwcert = nm_setting_vpn_get_secret (s_vpn, NM_OPENCONNECT_KEY_GWCERT);
+       if (!gateway || !cookie || !gwcert) {
+               if (cached_secrets_valid(plugin, s_vpn, uuid))
+                       return FALSE;
+
                *setting_name = NM_SETTING_VPN_SETTING_NAME;
                return TRUE;
        }
+
+       g_free(priv->uuid);
+       priv->uuid = g_strdup(uuid);
+       g_free(priv->cookie);
+       priv->cookie = g_strdup(cookie);
+       g_free(priv->gwcert);
+       priv->gwcert = g_strdup(gwcert);
+       g_free(priv->gateway);
+       priv->gateway = g_strdup(gateway);
+       priv->cookie_start = time(NULL);
        return FALSE;
 }
 
@@ -629,6 +672,18 @@ quit_mainloop (NMOPENCONNECTPlugin *plugin, gpointer user_data)
        g_main_loop_quit ((GMainLoop *) user_data);
 }
 
+static void got_config (NMOPENCONNECTPlugin *plugin, GHashTable *config,
+                                               gpointer user_data)
+{
+       NMOPENCONNECTPluginPrivate *priv = NM_OPENCONNECT_PLUGIN_GET_PRIVATE (plugin);
+       GValue *val = g_hash_table_lookup (config, "openconnect_session_timeout");
+
+       if (!val)
+               return;
+
+       priv->cookie_validity = g_value_get_uint(val);
+}
+
 int main (int argc, char *argv[])
 {
        NMOPENCONNECTPlugin *plugin;
@@ -684,6 +739,8 @@ int main (int argc, char *argv[])
        if (!persist)
                g_signal_connect (plugin, "quit", G_CALLBACK (quit_mainloop), loop);
 
+       g_signal_connect (plugin, "config", G_CALLBACK (got_config), plugin);
+
        setup_signals ();
        g_main_loop_run (loop);
 


-- 
dwmw2

Attachment: smime.p7s
Description: S/MIME cryptographic signature



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]