One of the behaviors that brought this to light was a user who had an LDAP (non-deferred) plugin followed by a Duo MFA (deferred) plugin. He noted that, even if the LDAP call returned failure, the Duo plugin was still called. That would generate a push notification to his phone even though the authentication was already destined to fail due to LDAP. It looks like this patch returns failure when the first plugin fails, so would resolve that scenario by not calling the Duo plugin at all if LDAP returned failure.
Is the source for the "auth-multi" plugin you tested with available anywhere? I could not find it, and had wanted something like it for my own testing. On Thu, Mar 10, 2022 at 12:18 PM Gert Doering <g...@greenie.muc.de> wrote: > Without this patch, OpenVPN behaviour if more than one plugin wants > to do deferred user/password authentication not well-defined, as > there is just one set of auth control files and a single plugin state. > > This patch changes "key state -> plugin_auth" from a single struct > to an array of MAX_PLUGINS elements that have per-plugin auth-control > files and auth status. > > All direct access is changed to iterating over this array. > > The actual plugin calls are no longer done with the "do them all" > function plugin_call() (or plugin_call_ssl()) but plugin.c/plugin.h > is changed to expose the "call one" function plugin_call_item(), and > verify_user_pass_plugin() calls this for each loaded plugin, > keeping track of "overall" state. > > key_state_test_plugin_auth() is introduced to do the > "key_state_test_auth_control_file()" test for all plugins, and > return "FAIL if one fails, PENDING if one is pending, SUCCESS > if one was pending and succeeded now". > > This was tested with the "auth-multi" test plugin, with 5-7 plugins > loaded (some deferred, some direct) and "some of them failing" or > "all succeeding". Testing included "will it leak files if multiple > deferred plugins are ongoing, and one of the earlier ones rejects > authentication". > > This patch is not suitable for production use: > - it's full of debug output > - it will break compilation without ENABLE_PLUGINS > - it stands to argue whether plugin_call_item() should be exposed, > or key_state_test_plugin_auth() might be moved to plugin.c instead > (with a null function if ENABLE_PLUGINS is not defined) > > Signed-off-by: Gert Doering <g...@greenie.muc.de> > --- > src/openvpn/plugin.c | 2 +- > src/openvpn/plugin.h | 9 +++ > src/openvpn/ssl.c | 5 +- > src/openvpn/ssl_common.h | 4 +- > src/openvpn/ssl_verify.c | 127 ++++++++++++++++++++++++++++++++------- > 5 files changed, 123 insertions(+), 24 deletions(-) > > diff --git a/src/openvpn/plugin.c b/src/openvpn/plugin.c > index e3a89293..74b57033 100644 > --- a/src/openvpn/plugin.c > +++ b/src/openvpn/plugin.c > @@ -518,7 +518,7 @@ plugin_open_item(struct plugin *p, > } > } > > -static int > +int > plugin_call_item(const struct plugin *p, > void *per_client_context, > const int type, > diff --git a/src/openvpn/plugin.h b/src/openvpn/plugin.h > index c6fa206a..799a646e 100644 > --- a/src/openvpn/plugin.h > +++ b/src/openvpn/plugin.h > @@ -132,6 +132,15 @@ int plugin_call_ssl(const struct plugin_list *pl, > int current_cert_depth, > openvpn_x509_cert_t *current_cert > ); > +int plugin_call_item(const struct plugin *p, > + void *per_client_context, > + const int type, > + const struct argv *av, > + struct openvpn_plugin_string_list **retlist, > + const char **envp, > + int certdepth, > + openvpn_x509_cert_t *current_cert > + ); > > void plugin_list_close(struct plugin_list *pl); > > diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c > index 14a943a7..ce6de9a0 100644 > --- a/src/openvpn/ssl.c > +++ b/src/openvpn/ssl.c > @@ -1036,7 +1036,10 @@ key_state_free(struct key_state *ks, bool clear) > > packet_id_free(&ks->crypto_options.packet_id); > > - key_state_rm_auth_control_files(&ks->plugin_auth); > + for (int i=0; i<MAX_PLUGINS; i++) > + { > + key_state_rm_auth_control_files(&ks->plugin_auth[i]); > + } > key_state_rm_auth_control_files(&ks->script_auth); > > if (clear) > diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h > index 8a077c74..0de3290a 100644 > --- a/src/openvpn/ssl_common.h > +++ b/src/openvpn/ssl_common.h > @@ -37,6 +37,8 @@ > > #include "ssl_backend.h" > > +#include "plugin.h" > + > /* passwords */ > #define UP_TYPE_AUTH "Auth" > #define UP_TYPE_PRIVATE_KEY "Private Key" > @@ -239,7 +241,7 @@ struct key_state > #endif > time_t acf_last_mod; > > - struct auth_deferred_status plugin_auth; > + struct auth_deferred_status plugin_auth[MAX_PLUGINS]; > struct auth_deferred_status script_auth; > }; > > diff --git a/src/openvpn/ssl_verify.c b/src/openvpn/ssl_verify.c > index 3b6b58fa..38c8a830 100644 > --- a/src/openvpn/ssl_verify.c > +++ b/src/openvpn/ssl_verify.c > @@ -1059,6 +1059,50 @@ key_state_test_auth_control_file(struct > auth_deferred_status *ads, bool cached) > return ACF_DISABLED; > } > > +/** > + * Checks the auth control status for all plugins. > + * > + * returns: > + * - ACF_PENDING if any plugin is still waiting for results. > + * - ACF_SUCCEEDED if there were pending plugins AND all succeeded > + * - ACF_FAILED if any plugin fails > + * - ACF_DISABLED if no plugin is waiting > + * > + * @param ads array of deferred status control structures > + * @param cached Return only cached status > + * @return ACF_* as per enum > + */ > +static enum auth_deferred_result > +key_state_test_plugin_auth(struct auth_deferred_status *ads, bool cached) > +{ > + unsigned int ret = ACF_DISABLED; > + > + for (int i=0;i<MAX_PLUGINS;i++) > + { > + unsigned int ret_one = > key_state_test_auth_control_file(&(ads[i]), cached); > +msg(M_INFO, "GERT(9a): kstpa: i=%d, ret_one=%d", i, ret_one); > + > + /* if at least one plugin fails, we fail all */ > + if ( ret_one == ACF_FAILED ) > + { > + ret = ret_one; > + break; > + } > + /* if at least one plugin is still pending, we're still pending */ > + if ( ret_one == ACF_PENDING ) > + { > + ret = ret_one; > + } > + /* if no plugins are pending AND we have success, we succeed */ > + if ( ret_one == ACF_SUCCEEDED && ret != ACF_PENDING ) > + { > + ret = ret_one; > + } > + } > +msg(M_INFO, "GERT(9b): kstpa: ret=%d", ret); > + return ret; > +} > + > /** > * This method takes a key_state and if updates the state > * of the key if it is deferred. > @@ -1069,6 +1113,7 @@ key_state_test_auth_control_file(struct > auth_deferred_status *ads, bool cached) > static void > update_key_auth_status(bool cached, struct key_state *ks) > { > +msg(M_INFO, "GERT(10) update_key_auth_status authenticated=%d", > ks->authenticated); > if (ks->authenticated == KS_AUTH_FALSE) > { > return; > @@ -1078,7 +1123,7 @@ update_key_auth_status(bool cached, struct key_state > *ks) > enum auth_deferred_result auth_plugin = ACF_DISABLED; > enum auth_deferred_result auth_script = ACF_DISABLED; > enum auth_deferred_result auth_man = ACF_DISABLED; > - auth_plugin = key_state_test_auth_control_file(&ks->plugin_auth, > cached); > + auth_plugin = key_state_test_plugin_auth(ks->plugin_auth, cached); > auth_script = key_state_test_auth_control_file(&ks->script_auth, > cached); > #ifdef ENABLE_MANAGEMENT > auth_man = man_def_auth_test(ks); > @@ -1357,40 +1402,80 @@ static int > verify_user_pass_plugin(struct tls_session *session, struct tls_multi > *multi, > const struct user_pass *up) > { > - int retval = OPENVPN_PLUGIN_FUNC_ERROR; > + int retval = OPENVPN_PLUGIN_FUNC_SUCCESS; > struct key_state *ks = &session->key[KS_PRIMARY]; /* primary key > */ > > + struct gc_arena gc = gc_new(); > + > /* set password in private env space */ > setenv_str(session->opt->es, "password", up->password); > > - /* generate filename for deferred auth control file */ > - if (!key_state_gen_auth_control_files(&ks->plugin_auth, session->opt)) > + /* iterate over list of plugins here, because deferred auth needs > + * per-plugin auth control files > + */ > + const struct plugin_list *pl = session->opt->plugins; > + int plugins_active = plugin_n(pl); > + ASSERT( plugins_active < SIZE(ks->plugin_auth) ); > + msg(M_INFO, "GERT(1): plugins_active=%d, SIZE=%d", plugins_active, > (int)SIZE(ks->plugin_auth) ); > + > + for (int i=0; i<plugins_active; i++) > { > - msg(D_TLS_ERRORS, "TLS Auth Error (%s): " > - "could not create deferred auth control file", __func__); > - return retval; > - } > + /* generate filename for deferred auth control file */ > + if (!key_state_gen_auth_control_files(&ks->plugin_auth[i], > session->opt)) > + { > + msg(D_TLS_ERRORS, "TLS Auth Error (%s): " > + "could not create deferred auth control file", __func__); > + retval = OPENVPN_PLUGIN_FUNC_ERROR; > + break; > + } > > - /* call command */ > - retval = plugin_call(session->opt->plugins, > OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es); > + msg(M_INFO, "GERT(4): plugin #%d, acf=%s acp=%s", i, > + ks->plugin_auth[i].auth_control_file, > ks->plugin_auth[i].auth_pending_file); > > - if (retval == OPENVPN_PLUGIN_FUNC_DEFERRED) > - { > - /* Check if the plugin has written the pending auth control > - * file and send the pending auth to the client */ > - if(!key_state_check_auth_pending_file(&ks->plugin_auth, multi)) > + /* call command */ > + // retval = plugin_call_item(session->opt->plugins, > OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, NULL, NULL, session->opt->es); > + // pl type > av pr es [crt -1, NULL] > + > + const char **envp = make_env_array(session->opt->es, false, &gc); > + const int status = plugin_call_item(&pl->common->plugins[i], > + > pl->per_client.per_client_context[i], > + > OPENVPN_PLUGIN_AUTH_USER_PASS_VERIFY, > + NULL, NULL, envp, -1, NULL ); > + > + if (status == OPENVPN_PLUGIN_FUNC_DEFERRED) > + { > + /* Check if the plugin has written the pending auth control > + * file and send the pending auth to the client */ > + if(!key_state_check_auth_pending_file(&(ks->plugin_auth[i]), > multi)) > + { > + retval = OPENVPN_PLUGIN_FUNC_ERROR; > + key_state_rm_auth_control_files(&(ks->plugin_auth[i])); > + break; > + } > + else > + { > + retval = OPENVPN_PLUGIN_FUNC_DEFERRED; > + } > + } > + else > + { > + /* purge auth control filename (and file itself) for > non-deferred returns */ > + key_state_rm_auth_control_files(&(ks->plugin_auth[i])); > + ks->plugin_auth[i].auth_control_status = ACF_DISABLED; > + } > + > + /* if a single plugin fails, we fail all */ > + if (status == OPENVPN_PLUGIN_FUNC_ERROR) > { > retval = OPENVPN_PLUGIN_FUNC_ERROR; > - key_state_rm_auth_control_files(&ks->plugin_auth); > + break; > } > - } > - else > - { > - /* purge auth control filename (and file itself) for non-deferred > returns */ > - key_state_rm_auth_control_files(&ks->plugin_auth); > + > + msg(M_INFO, "GERT(3): plugin #%d status=%d -> retval=%d", i, > status, retval ); > } > > setenv_del(session->opt->es, "password"); > + gc_free(&gc); > > return retval; > } > -- > 2.34.1 > > > > _______________________________________________ > Openvpn-devel mailing list > Openvpn-devel@lists.sourceforge.net > https://lists.sourceforge.net/lists/listinfo/openvpn-devel >
_______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel