From 710fc76887f4e8d62b68a0e1f40520d0eb28e740 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Tue, 9 Feb 2021 21:15:54 +0100 Subject: [PATCH] * Restconf authentication callback (ca_auth) signature changed * Not backward compatible: All uses of the ca-auth callback in restconf plugins must be changed * New version is: `int ca_auth(h, req, auth_type, authp, userp)` * where `auth_type` is the requested authentication-type (none, client-cert or user-defined) * `authp` is the returned authentication flag * `userp` is the returned associated authenticated user * and the return value is three-valued: -1: Error, 0: ignored, 1: OK * For more info see [clixon-docs](https://clixon-docs.readthedocs.io/en/latest/restconf.html) * New clixon-restconf@2020-12-30.yang revision --- CHANGELOG.md | 21 +- apps/backend/Makefile.in | 1 + apps/backend/backend_main.c | 166 +------------ apps/backend/backend_plugin_restconf.c | 223 ++++++++++++++++++ apps/backend/backend_plugin_restconf.h | 43 ++++ apps/restconf/restconf_handle.c | 32 +++ apps/restconf/restconf_handle.h | 2 + apps/restconf/restconf_lib.c | 123 +++++++++- apps/restconf/restconf_lib.h | 2 + apps/restconf/restconf_main_evhtp.c | 105 +++++---- apps/restconf/restconf_main_fcgi.c | 28 ++- apps/restconf/restconf_root.c | 42 +--- apps/restconf/restconf_stream_fcgi.c | 28 +-- example/main/example_restconf.c | 128 +++++----- lib/clixon/clixon_plugin.h | 62 ++++- lib/src/clixon_plugin.c | 116 ++++++--- test/lib.sh | 39 ++- test/test_api.sh | 6 + test/test_augment.sh | 6 + test/test_choice.sh | 6 + test/test_client.sh | 6 + test/test_copy_config.sh | 6 + test/test_identity.sh | 6 + test/test_nacm.sh | 10 +- test/test_nacm_datanode.sh | 10 +- test/test_nacm_datanode_paths.sh | 10 +- test/test_nacm_datanode_read.sh | 10 +- test/test_nacm_datanode_write.sh | 10 +- test/test_nacm_default.sh | 10 +- test/test_nacm_ext.sh | 10 +- test/test_nacm_module_read.sh | 10 +- test/test_nacm_module_write.sh | 10 +- test/test_nacm_protocol.sh | 11 +- test/test_nacm_recovery.sh | 10 +- test/test_perf_restconf.sh | 6 + test/test_perf_state.sh | 5 + test/test_perf_state_only.sh | 6 + test/test_restconf.sh | 103 ++++---- test/test_restconf2.sh | 6 + test/test_restconf_err.sh | 6 + test/test_restconf_jukebox.sh | 6 + test/test_restconf_listkey.sh | 6 + test/test_restconf_netns.sh | 2 +- test/test_restconf_notifications.sh | 7 + test/test_restconf_patch.sh | 15 +- test/test_restconf_rpc.sh | 6 + ...sl_certs.sh => test_restconf_ssl_certs.sh} | 27 ++- test/test_restconf_startup.sh | 6 + test/test_rpc.sh | 6 + test/test_submodule.sh | 6 + test/test_yang_anydata.sh | 6 + test/test_yang_namespace.sh | 6 + yang/clixon/Makefile.in | 2 +- yang/clixon/clixon-restconf@2020-12-30.yang | 160 +++++++++++++ 54 files changed, 1216 insertions(+), 485 deletions(-) create mode 100644 apps/backend/backend_plugin_restconf.c create mode 100644 apps/backend/backend_plugin_restconf.h rename test/{test_ssl_certs.sh => test_restconf_ssl_certs.sh} (86%) create mode 100644 yang/clixon/clixon-restconf@2020-12-30.yang diff --git a/CHANGELOG.md b/CHANGELOG.md index a6970232..a932817a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ Expected: February 2021 ### New features * NETCONF Call Home Call Home RFC 8071 + * See [Netconf/ssh callhome](https://clixon-docs.readthedocs.io/en/latest/netconf.html#callhome) * Solution description using openssh and utility functions, no changes to core clixon * Example: test/test_netconf_ssh_callhome.sh * RESTCONF Call home not done @@ -42,6 +43,14 @@ Expected: February 2021 Developers may need to change their code +* Restconf authentication callback (ca_auth) signature changed + * Not backward compatible: All uses of the ca-auth callback in restconf plugins must be changed + * New version is: `int ca_auth(h, req, auth_type, authp, userp)` + * where `auth_type` is the requested authentication-type (none, client-cert or user-defined) + * `authp` is the returned authentication flag + * `userp` is the returned associated authenticated user + * and the return value is three-valued: -1: Error, 0: ignored, 1: OK + * For more info see [clixon-docs](https://clixon-docs.readthedocs.io/en/latest/restconf.html) * rpc msg C API rearranged to separate socket/connect from connect * Added `cvv_i` output parameter to `api_path_fmt2api_path()` to see how many cvv entries were used. @@ -50,6 +59,10 @@ Developers may need to change their code Users may have to change how they access the system * Handling empty netconf XML messages "]]>]]>" is changed from being accepted to return an error. +* New clixon-restconf@2020-12-30.yang revision + * Added: debug field + * Added 'none' as default value for auth-type + * Changed http-auth-type enum from 'password' to 'user' * New clixon-lib@2020-12-30.yang revision * Changed: RPC process-control output parameter status to pid * New clixon-config@2020-12-30.yang revision @@ -710,7 +723,7 @@ Patch release based on testing by Dave Cornejo, Netgate ### Summary -The main improvement in this release concerns security in terms of priveleges and credentials of accessing the clixon backend. There is also stricter multi-namespace checks which primarily effects where augmented models are used. +The main improvement in this release concerns security in terms of privileges and credentials of accessing the clixon backend. There is also stricter multi-namespace checks which primarily effects where augmented models are used. ### Major New features * The backend daemon can drop privileges after initialization to run as non-privileged user @@ -718,7 +731,7 @@ The main improvement in this release concerns security in terms of priveleges an * use `-U ` clixon_backend command-line option to drop to `user` * Generic options are the following: * `CLICON_BACKEND_USER` drop of privileges to this user - * `CLICON_BACKEND_PRIVELEGES` can have the following values: + * `CLICON_BACKEND_PRIVILEGES` can have the following values: * `none` Make no drop/change in privileges. This is currently the default. * `drop_perm` After initialization, drop privileges permanently * `drop_perm` After initialization, drop privileges temporarily (to a euid) @@ -749,8 +762,8 @@ a="urn:example:a" xmlns:b="urn:example:b"/>` * New clixon-config@2019-09-11.yang revision * Added: CLICON_BACKEND_USER: Drop of privileges to this user, owner of backend socket (default: `clicon`) * Therefore new installation should now add a UNIX `clicon` user - * Added: CLICON_BACKEND_PRIVELEGES: If and how to drop privileges - * Added: CLICON_NACM_CREDENTIALS: If and how to check backend socket priveleges with NACM + * Added: CLICON_BACKEND_PRIVILEGES: If and how to drop privileges + * Added: CLICON_NACM_CREDENTIALS: If and how to check backend socket privileges with NACM * Added: CLICON_NACM_RECOVERY_USER: Name of NACM recovery user. * Restconf top-level operations GET root resource modified to comply with RFC 8040 Sec 3.1 * non-pretty print remove all spaces, eg `{"operations":{"clixon-example:client-rpc":[null]` diff --git a/apps/backend/Makefile.in b/apps/backend/Makefile.in index b30a5a74..b45d59fd 100644 --- a/apps/backend/Makefile.in +++ b/apps/backend/Makefile.in @@ -86,6 +86,7 @@ APPSRC += backend_socket.c APPSRC += backend_client.c APPSRC += backend_commit.c APPSRC += backend_plugin.c +APPSRC += backend_plugin_restconf.c # Pseudo plugin for restconf daemon APPSRC += backend_startup.c APPOBJ = $(APPSRC:.c=.o) diff --git a/apps/backend/backend_main.c b/apps/backend/backend_main.c index 7dda16b9..1e95d963 100644 --- a/apps/backend/backend_main.c +++ b/apps/backend/backend_main.c @@ -75,6 +75,7 @@ #include "backend_commit.h" #include "backend_handle.h" #include "backend_startup.h" +#include "backend_plugin_restconf.h" /* Command line options to be passed to getopt(3) */ #define BACKEND_OPTS "hD:f:E:l:d:p:b:Fza:u:P:1qs:c:U:g:y:o:" @@ -388,169 +389,6 @@ ret2status(int ret, return retval; } -/*--------------------------------------------------------------------- - * Restconf process pseudo plugin - */ - -#define RESTCONF_PROCESS "restconf" - -/*! Process rpc callback function - * - if RPC op is start, if enable is true, start the service, if false, error or ignore it - * - if RPC op is stop, stop the service - * These rules give that if RPC op is start and enable is false -> change op to none - */ -int -restconf_rpc_wrapper(clicon_handle h, - process_entry_t *pe, - char **operation) -{ - int retval = -1; - cxobj *xt = NULL; - - clicon_debug(1, "%s", __FUNCTION__); - if (strcmp(*operation, "stop") == 0){ - /* if RPC op is stop, stop the service */ - } - else if (strcmp(*operation, "start") == 0){ - /* RPC op is start & enable is true, then start the service, - & enable is false, error or ignore it */ - if (xmldb_get(h, "running", NULL, "/restconf", &xt) < 0) - goto done; - if (xt != NULL && - xpath_first(xt, NULL, "/restconf[enable='false']") != NULL) { - *operation = "none"; - } - } - retval = 0; - done: - if (xt) - xml_free(xt); - return retval; -} - -/*! Enable process-control of restconf daemon, ie start/stop restconf by registering restconf process - * @param[in] h Clicon handle - * @note Could also look in clixon-restconf and start process if enable is true, but that needs to - * be in start callback using a pseudo plugin. - */ -static int -restconf_pseudo_process_control(clicon_handle h) -{ - int retval = -1; - char **argv = NULL; - int i; - int nr; - char dbgstr[8]; - char wwwstr[64]; - - nr = 4; - if (clicon_debug_get() != 0) - nr += 2; - if ((argv = calloc(nr, sizeof(char *))) == NULL){ - clicon_err(OE_UNIX, errno, "calloc"); - goto done; - } - i = 0; - snprintf(wwwstr, sizeof(wwwstr)-1, "%s/clixon_restconf", clicon_option_str(h, "CLICON_WWWDIR")); - argv[i++] = wwwstr; - argv[i++] = "-f"; - argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); - if (clicon_debug_get() != 0){ - argv[i++] = "-D"; - snprintf(dbgstr, sizeof(dbgstr)-1, "%d", clicon_debug_get()); - argv[i++] = dbgstr; - } - argv[i++] = NULL; - assert(i==nr); - if (clixon_process_register(h, RESTCONF_PROCESS, - NULL /* XXX network namespace */, - restconf_rpc_wrapper, - argv, nr) < 0) - goto done; - if (argv != NULL) - free(argv); - retval = 0; - done: - return retval; -} - -/*! Restconf pseduo-plugin process validate - */ -static int -restconf_pseudo_process_validate(clicon_handle h, - transaction_data td) -{ - int retval = -1; - cxobj *xtarget; - - clicon_debug(1, "%s", __FUNCTION__); - xtarget = transaction_target(td); - /* If ssl-enable is true and (at least a) socket has ssl, - * then server-cert-path and server-key-path must exist */ - if (xpath_first(xtarget, NULL, "restconf/enable[.='true']") && - xpath_first(xtarget, NULL, "restconf/socket[ssl='true']")){ - /* Should filepath be checked? One could claim this is a runtime system,... */ - if (xpath_first(xtarget, 0, "restconf/server-cert-path") == NULL){ - clicon_err(OE_CFG, 0, "SSL enabled but server-cert-path not set"); - return -1; /* induce fail */ - } - if (xpath_first(xtarget, 0, "restconf/server-key-path") == NULL){ - clicon_err(OE_CFG, 0, "SSL enabled but server-key-path not set"); - return -1; /* induce fail */ - } - } - retval = 0; - return retval; -} - -/*! Restconf pseduo-plugin process commit - */ -static int -restconf_pseudo_process_commit(clicon_handle h, - transaction_data td) -{ - int retval = -1; - cxobj *xtarget; - cxobj *cx; - int enabled = 0; - - clicon_debug(1, "%s", __FUNCTION__); - xtarget = transaction_target(td); - if (xpath_first(xtarget, NULL, "/restconf[enable='true']") != NULL) - enabled++; - if ((cx = xpath_first(xtarget, NULL, "/restconf/enable")) != NULL && - xml_flag(cx, XML_FLAG_CHANGE|XML_FLAG_ADD)){ - if (clixon_process_operation(h, RESTCONF_PROCESS, - enabled?"start":"stop", 0, NULL) < 0) - goto done; - } - retval = 0; - done: - return retval; -} - -/*! Register start/stop restconf RPC and create pseudo-plugin to monitor enable flag - * @param[in] h Clixon handle - */ -static int -restconf_pseudo_process_reg(clicon_handle h, - yang_stmt *yspec) -{ - int retval = -1; - clixon_plugin *cp = NULL; - - if (clixon_pseudo_plugin(h, "restconf pseudo plugin", &cp) < 0) - goto done; - cp->cp_api.ca_trans_commit = restconf_pseudo_process_commit; - cp->cp_api.ca_trans_validate = restconf_pseudo_process_validate; - - /* Register generic process-control of restconf daemon, ie start/stop restconf */ - if (restconf_pseudo_process_control(h) < 0) - goto done; - retval = 0; - done: - return retval; -} /* Debug timer */ int @@ -981,7 +819,7 @@ main(int argc, goto done; /* Check restconf start/stop from backend */ if (clicon_option_bool(h, "CLICON_BACKEND_RESTCONF_PROCESS")){ - if (restconf_pseudo_process_reg(h, yspec) < 0) + if (backend_plugin_restconf_register(h, yspec) < 0) goto done; } /* Here all modules are loaded diff --git a/apps/backend/backend_plugin_restconf.c b/apps/backend/backend_plugin_restconf.c new file mode 100644 index 00000000..b469e273 --- /dev/null +++ b/apps/backend/backend_plugin_restconf.c @@ -0,0 +1,223 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * + * Pseudo backend plugin for starting restconf daemon + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "clixon_backend_transaction.h" +#include "backend_plugin_restconf.h" + +/*--------------------------------------------------------------------- + * Restconf process pseudo plugin + */ + +#define RESTCONF_PROCESS "restconf" + +/*! Process rpc callback function + * - if RPC op is start, if enable is true, start the service, if false, error or ignore it + * - if RPC op is stop, stop the service + * These rules give that if RPC op is start and enable is false -> change op to none + */ +int +restconf_rpc_wrapper(clicon_handle h, + process_entry_t *pe, + char **operation) +{ + int retval = -1; + cxobj *xt = NULL; + + clicon_debug(1, "%s", __FUNCTION__); + if (strcmp(*operation, "stop") == 0){ + /* if RPC op is stop, stop the service */ + } + else if (strcmp(*operation, "start") == 0){ + /* RPC op is start & enable is true, then start the service, + & enable is false, error or ignore it */ + if (xmldb_get(h, "running", NULL, "/restconf", &xt) < 0) + goto done; + if (xt != NULL && + xpath_first(xt, NULL, "/restconf[enable='false']") != NULL) { + *operation = "none"; + } + } + retval = 0; + done: + if (xt) + xml_free(xt); + return retval; +} + +/*! Enable process-control of restconf daemon, ie start/stop restconf by registering restconf process + * @param[in] h Clicon handle + * @note Could also look in clixon-restconf and start process if enable is true, but that needs to + * be in start callback using a pseudo plugin. + */ +static int +restconf_pseudo_process_control(clicon_handle h) +{ + int retval = -1; + char **argv = NULL; + int i; + int nr; + char dbgstr[8]; + char wwwstr[64]; + + nr = 4; + if (clicon_debug_get() != 0) + nr += 2; + if ((argv = calloc(nr, sizeof(char *))) == NULL){ + clicon_err(OE_UNIX, errno, "calloc"); + goto done; + } + i = 0; + snprintf(wwwstr, sizeof(wwwstr)-1, "%s/clixon_restconf", clicon_option_str(h, "CLICON_WWWDIR")); + argv[i++] = wwwstr; + argv[i++] = "-f"; + argv[i++] = clicon_option_str(h, "CLICON_CONFIGFILE"); + /* Add debug if backend has debug. + * There is also a debug flag in clixon-restconf.yang but it kicks in after it starts + */ + if (clicon_debug_get() != 0){ + argv[i++] = "-D"; + snprintf(dbgstr, sizeof(dbgstr)-1, "%d", clicon_debug_get()); + argv[i++] = dbgstr; + } + argv[i++] = NULL; + assert(i==nr); + if (clixon_process_register(h, RESTCONF_PROCESS, + NULL /* XXX network namespace */, + restconf_rpc_wrapper, + argv, nr) < 0) + goto done; + if (argv != NULL) + free(argv); + retval = 0; + done: + return retval; +} + +/*! Restconf pseduo-plugin process validate + */ +static int +restconf_pseudo_process_validate(clicon_handle h, + transaction_data td) +{ + int retval = -1; + cxobj *xtarget; + + clicon_debug(1, "%s", __FUNCTION__); + xtarget = transaction_target(td); + /* If ssl-enable is true and (at least a) socket has ssl, + * then server-cert-path and server-key-path must exist */ + if (xpath_first(xtarget, NULL, "restconf/enable[.='true']") && + xpath_first(xtarget, NULL, "restconf/socket[ssl='true']")){ + /* Should filepath be checked? One could claim this is a runtime system,... */ + if (xpath_first(xtarget, 0, "restconf/server-cert-path") == NULL){ + clicon_err(OE_CFG, 0, "SSL enabled but server-cert-path not set"); + return -1; /* induce fail */ + } + if (xpath_first(xtarget, 0, "restconf/server-key-path") == NULL){ + clicon_err(OE_CFG, 0, "SSL enabled but server-key-path not set"); + return -1; /* induce fail */ + } + } + retval = 0; + return retval; +} + +/*! Restconf pseduo-plugin process commit + */ +static int +restconf_pseudo_process_commit(clicon_handle h, + transaction_data td) +{ + int retval = -1; + cxobj *xtarget; + cxobj *cx; + int enabled = 0; + + clicon_debug(1, "%s", __FUNCTION__); + xtarget = transaction_target(td); + if (xpath_first(xtarget, NULL, "/restconf[enable='true']") != NULL) + enabled++; + if ((cx = xpath_first(xtarget, NULL, "/restconf/enable")) != NULL && + xml_flag(cx, XML_FLAG_CHANGE|XML_FLAG_ADD)){ + if (clixon_process_operation(h, RESTCONF_PROCESS, + enabled?"start":"stop", 0, NULL) < 0) + goto done; + } + retval = 0; + done: + return retval; +} + +/*! Register start/stop restconf RPC and create pseudo-plugin to monitor enable flag + * @param[in] h Clixon handle + */ +int +backend_plugin_restconf_register(clicon_handle h, + yang_stmt *yspec) +{ + int retval = -1; + clixon_plugin *cp = NULL; + + if (clixon_pseudo_plugin(h, "restconf pseudo plugin", &cp) < 0) + goto done; + cp->cp_api.ca_trans_commit = restconf_pseudo_process_commit; + cp->cp_api.ca_trans_validate = restconf_pseudo_process_validate; + + /* Register generic process-control of restconf daemon, ie start/stop restconf */ + if (restconf_pseudo_process_control(h) < 0) + goto done; + retval = 0; + done: + return retval; +} diff --git a/apps/backend/backend_plugin_restconf.h b/apps/backend/backend_plugin_restconf.h new file mode 100644 index 00000000..9bf54895 --- /dev/null +++ b/apps/backend/backend_plugin_restconf.h @@ -0,0 +1,43 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020-2021 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * + * Pseudo backend plugin for starting restconf daemon + */ + +#ifndef _BACKEND_PLUGIN_RESTCONF_H_ +#define _BACKEND_PLUGIN_RESTCONF_H_ + +int backend_plugin_restconf_register(clicon_handle h, yang_stmt *yspec); + +#endif /* _BACKEND_PLUGIN_RESTCONF_H_ */ diff --git a/apps/restconf/restconf_handle.c b/apps/restconf/restconf_handle.c index 6e4530ac..ef413d08 100644 --- a/apps/restconf/restconf_handle.c +++ b/apps/restconf/restconf_handle.c @@ -61,6 +61,7 @@ /* clicon */ #include +#include "restconf_lib.h" #include "restconf_handle.h" /* header part is copied from struct clicon_handle in lib/src/clixon_handle.c */ @@ -90,6 +91,7 @@ struct restconf_handle { /* ------ end of common handle ------ */ clicon_hash_t *rh_params; /* restconf parameters, including http headers */ + clixon_auth_type_t rh_auth_type; /* authentication type */ }; /*! Creates and returns a clicon config handle for other CLICON API calls @@ -172,3 +174,33 @@ restconf_param_del_all(clicon_handle h) done: return retval; } + +/*! Get restconf http parameter + * @param[in] h Clicon handle + * @retval auth_type + */ +clixon_auth_type_t +restconf_auth_type_get(clicon_handle h) +{ + struct restconf_handle *rh = handle(h); + + return rh->rh_auth_type; +} + +/*! Set restconf http parameter + * @param[in] h Clicon handle + * @param[in] name Data name + * @param[in] val Data value as null-terminated string + * @retval 0 OK + * @retval -1 Error + * Currently using clixon runtime data but there is risk for colliding names + */ +int +restconf_auth_type_set(clicon_handle h, + clixon_auth_type_t type) +{ + struct restconf_handle *rh = handle(h); + + rh->rh_auth_type = type; + return 0; +} diff --git a/apps/restconf/restconf_handle.h b/apps/restconf/restconf_handle.h index df545059..9d5304ea 100644 --- a/apps/restconf/restconf_handle.h +++ b/apps/restconf/restconf_handle.h @@ -47,5 +47,7 @@ int restconf_handle_exit(clicon_handle h); char *restconf_param_get(clicon_handle h, const char *param); int restconf_param_set(clicon_handle h, const char *param, char *val); int restconf_param_del_all(clicon_handle h); +clixon_auth_type_t restconf_auth_type_get(clicon_handle h); +int restconf_auth_type_set(clicon_handle h, clixon_auth_type_t type); #endif /* _RESTCONF_HANDLE_H_ */ diff --git a/apps/restconf/restconf_lib.c b/apps/restconf/restconf_lib.c index 3c6dee4c..7faa943d 100644 --- a/apps/restconf/restconf_lib.c +++ b/apps/restconf/restconf_lib.c @@ -60,8 +60,9 @@ #include #include "restconf_api.h" -#include "restconf_handle.h" #include "restconf_lib.h" +#include "restconf_err.h" +#include "restconf_handle.h" /* See RFC 8040 Section 7: Mapping from NETCONF to Status Code * and RFC 6241 Appendix A. NETCONF Error list @@ -495,3 +496,123 @@ restconf_drop_privileges(clicon_handle h, return retval; } +/*! + * @param[in] h Clicon handle + * @param[in] req Generic Www handle (can be part of clixon handle) + * @retval -1 Error + * @retval 0 Not authenticated + * @retval 1 Authenticated + */ +int +restconf_authentication_cb(clicon_handle h, + void *req, + int pretty, + restconf_media media_out) +{ + int retval = -1; + clixon_auth_type_t auth_type; + int authenticated; + int ret; + char *username = NULL; + cxobj *xret = NULL; + cxobj *xerr; + + auth_type = restconf_auth_type_get(h); + clicon_debug(1, "%s auth-type:%s", __FUNCTION__, clixon_auth_type_int2str(auth_type)); + ret = 0; + authenticated = 0; + if (auth_type != CLIXON_AUTH_NONE) + if ((ret = clixon_plugin_auth_all(h, req, + auth_type, + &authenticated, + &username)) < 0) + goto done; + if (ret == 1){ /* OK, tag username to handle */ + clicon_username_set(h, username); + } + else { /* Default behaviour */ + switch (auth_type){ + case CLIXON_AUTH_NONE: + clicon_username_set(h, "none"); + authenticated = 1; + break; + case CLIXON_AUTH_CLIENT_CERTIFICATE: { + char *cn; + /* Check for cert subject common name (CN) */ + if ((cn = restconf_param_get(h, "SSL_CN")) != NULL){ + clicon_username_set(h, cn); + authenticated = 1; + } + break; + } + case CLIXON_AUTH_USER: + authenticated = 0; + break; + } + } + if (authenticated == 0){ /* Message is not authenticated (401 returned) */ + if (netconf_access_denied_xml(&xret, "protocol", "The requested URL was unauthorized") < 0) + goto done; + if ((xerr = xpath_first(xret, NULL, "//rpc-error")) != NULL){ + if (api_return_err(h, req, xerr, pretty, media_out, 0) < 0) + goto done; + goto notauth; + } + retval = 0; + goto notauth; + } + /* If set but no user, set a dummy user */ + retval = 1; + done: + clicon_debug(1, "%s retval:%d authenticated:%d user:%s", + __FUNCTION__, retval, authenticated, clicon_username_get(h)); + if (xret) + xml_free(xret); + return retval; + notauth: + retval = 0; + goto done; +} + +/*! Basic config init + * @param[in] h Clixon handle + * @param[in] xrestconf XML config containing clixon-restconf top-level + * @retval -1 Error + * @retval 0 Restconf is disable + * @retval 1 OK + */ +int +restconf_config_init(clicon_handle h, + cxobj *xrestconf) +{ + int retval = -1; + char *enable; + cxobj *x; + char *bstr; + cvec *nsc = NULL; + clixon_auth_type_t auth_type; + + if ((x = xpath_first(xrestconf, nsc, "enable")) != NULL && + (enable = xml_body(x)) != NULL){ + if (strcmp(enable, "false") == 0){ + clicon_debug(1, "%s restconf disabled", __FUNCTION__); + goto disable; + } + } + + /* get common fields */ + if ((x = xpath_first(xrestconf, nsc, "auth-type")) != NULL && + (bstr = xml_body(x)) != NULL){ + if ((auth_type = clixon_auth_type_str2int(bstr)) < 0){ + clicon_err(OE_CFG, EFAULT, "Invalid restconf auth-type: %s", bstr); + goto done; + } + restconf_auth_type_set(h, auth_type); + } + retval = 1; + done: + return retval; + disable: + retval = 0; + goto done; +} diff --git a/apps/restconf/restconf_lib.h b/apps/restconf/restconf_lib.h index 827a44fd..3c8354a1 100644 --- a/apps/restconf/restconf_lib.h +++ b/apps/restconf/restconf_lib.h @@ -82,6 +82,8 @@ int restconf_insert_attributes(cxobj *xdata, cvec *qvec); int restconf_main_extension_cb(clicon_handle h, yang_stmt *yext, yang_stmt *ys); char *restconf_uripath(clicon_handle h); int restconf_drop_privileges(clicon_handle h, char *user); +int restconf_authentication_cb(clicon_handle h, void *req, int pretty, restconf_media media_out); +int restconf_config_init(clicon_handle h, cxobj *xrestconf); #endif /* _RESTCONF_LIB_H_ */ diff --git a/apps/restconf/restconf_main_evhtp.c b/apps/restconf/restconf_main_evhtp.c index fe280f57..bb994fa0 100644 --- a/apps/restconf/restconf_main_evhtp.c +++ b/apps/restconf/restconf_main_evhtp.c @@ -83,19 +83,22 @@ #include "restconf_err.h" #include "restconf_root.h" - /* Command line options to be passed to getopt(3) */ #define RESTCONF_OPTS "hD:f:E:l:p:d:y:a:u:ro:" /* See see listen(5) */ #define SOCKET_LISTEN_BACKLOG 16 -/* clixon evhtp handle */ +/* Clixon evhtp handle + * Global data about evhtp lib, + * See evhtp_request_t *req for per-message state data + */ typedef struct { - evhtp_t **eh_htpvec; /* One per socket */ - int eh_htplen; /* Number of sockets */ - struct event_base *eh_evbase; /* Change to list */ - evhtp_ssl_cfg_t *eh_ssl_config; + clicon_handle eh_h; + evhtp_t **eh_htpvec; /* One per socket */ + int eh_htplen; /* Number of sockets */ + struct event_base *eh_evbase; /* Change to list */ + evhtp_ssl_cfg_t *eh_ssl_config; } cx_evhtp_handle; /* Need this global to pass to signal handler @@ -150,7 +153,7 @@ restconf_sig_term(int arg) exit(-1); if (_EVHTP_HANDLE) /* global */ evhtp_terminate(_EVHTP_HANDLE); - if (_CLICON_HANDLE){ + if (_CLICON_HANDLE){ /* could be replaced by eh->eh_h */ // stream_child_freeall(_CLICON_HANDLE); restconf_terminate(_CLICON_HANDLE); } @@ -448,8 +451,9 @@ static void cx_path_wellknown(evhtp_request_t *req, void *arg) { - clicon_handle h = arg; - int ret; + cx_evhtp_handle *eh = (cx_evhtp_handle*)arg; + clicon_handle h = eh->eh_h; + int ret; clicon_debug(1, "------------"); /* input debug */ @@ -479,15 +483,15 @@ static void cx_path_restconf(evhtp_request_t *req, void *arg) { - clicon_handle h = arg; - int ret; - cvec *qvec = NULL; + cx_evhtp_handle *eh = (cx_evhtp_handle*)arg; + clicon_handle h = eh->eh_h; + int ret; + cvec *qvec = NULL; clicon_debug(1, "------------"); /* input debug */ if (clicon_debug_get()) evhtp_headers_for_each(req->headers_in, print_header, h); - /* get accepted connection */ /* Query vector, ie the ?a=x&b=y stuff */ @@ -844,8 +848,7 @@ cx_evhtp_socket(clicon_handle h, cvec *nsc, char *server_cert_path, char *server_key_path, - char *server_ca_cert_path, - int auth_type_client_certificate) + char *server_ca_cert_path) { int retval = -1; char *netns = NULL; @@ -862,7 +865,6 @@ cx_evhtp_socket(clicon_handle h, clicon_err(OE_UNIX, errno, "evhtp_new"); goto done; } - #ifndef EVHTP_DISABLE_EVTHR /* threads */ evhtp_use_threads_wexit(htp, NULL, NULL, 4, NULL); #endif @@ -871,12 +873,12 @@ cx_evhtp_socket(clicon_handle h, /* Callback right after a connection is accepted. */ evhtp_set_post_accept_cb(htp, cx_post_accept, h); /* Callback to be executed for all /restconf api calls */ - if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, h) == NULL){ + if (evhtp_set_cb(htp, "/" RESTCONF_API, cx_path_restconf, eh) == NULL){ clicon_err(OE_EVENTS, errno, "evhtp_set_cb"); goto done; } /* Callback to be executed for all /restconf api calls */ - if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, h) == NULL){ + if (evhtp_set_cb(htp, RESTCONF_WELL_KNOWN, cx_path_wellknown, eh) == NULL){ clicon_err(OE_EVENTS, errno, "evhtp_set_cb"); goto done; } @@ -933,41 +935,41 @@ cx_evhtp_init(clicon_handle h, cvec *nsc, cx_evhtp_handle *eh) { - int retval = -1; - char* enable; - int ssl_enable = 0; - cxobj **vec = NULL; - size_t veclen; - char *server_cert_path = NULL; - char *server_key_path = NULL; - char *server_ca_cert_path = NULL; - char *auth_type = NULL; - int auth_type_client_certificate = 0; - cxobj *x; - int i; + int retval = -1; + int ssl_enable = 0; + int dbg = 0; + cxobj **vec = NULL; + size_t veclen; + char *server_cert_path = NULL; + char *server_key_path = NULL; + char *server_ca_cert_path = NULL; + cxobj *x; + char *bstr; + int i; + int ret; + clixon_auth_type_t auth_type; clicon_debug(1, "%s", __FUNCTION__); - if ((x = xpath_first(xrestconf, nsc, "enable")) != NULL && - (enable = xml_body(x)) != NULL){ - if (strcmp(enable, "false") == 0){ - clicon_debug(1, "%s restconf disabled", __FUNCTION__); - goto disable; - } - } + if ((ret = restconf_config_init(h, xrestconf)) < 0) + goto done; + if (ret == 0) + goto disable; + auth_type = restconf_auth_type_get(h); /* If at least one socket has ssl then enable global ssl_enable */ ssl_enable = xpath_first(xrestconf, nsc, "socket[ssl='true']") != NULL; - /* get common fields */ - if ((x = xpath_first(xrestconf, nsc, "auth-type")) != NULL) - auth_type = xml_body(x); - if (auth_type && strcmp(auth_type, "client-certificate") == 0) - auth_type_client_certificate = 1; + if ((x = xpath_first(xrestconf, nsc, "server-cert-path")) != NULL) server_cert_path = xml_body(x); if ((x = xpath_first(xrestconf, nsc, "server-key-path")) != NULL) server_key_path = xml_body(x); if ((x = xpath_first(xrestconf, nsc, "server-ca-cert-path")) != NULL) server_ca_cert_path = xml_body(x); - + if ((x = xpath_first(xrestconf, nsc, "debug")) != NULL && + (bstr = xml_body(x)) != NULL){ + dbg = atoi(bstr); + clicon_debug_init(dbg, NULL); + } + /* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */ if (ssl_enable){ /* Init evhtp ssl config struct */ @@ -982,11 +984,11 @@ cx_evhtp_init(clicon_handle h, if (cx_get_ssl_server_certs(h, server_cert_path, server_key_path, eh->eh_ssl_config) < 0) goto done; /* If client auth get client CA cert */ - if (auth_type_client_certificate) + if (auth_type == CLIXON_AUTH_CLIENT_CERTIFICATE) if (cx_get_ssl_client_ca_certs(h, server_ca_cert_path, eh->eh_ssl_config) < 0) goto done; eh->eh_ssl_config->x509_verify_cb = cx_verify_certs; /* Is extra verification necessary? */ - if (auth_type_client_certificate){ + if (auth_type == CLIXON_AUTH_CLIENT_CERTIFICATE){ eh->eh_ssl_config->verify_peer = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT; eh->eh_ssl_config->x509_verify_cb = cx_verify_certs; eh->eh_ssl_config->verify_depth = 2; @@ -998,8 +1000,7 @@ cx_evhtp_init(clicon_handle h, goto done; for (i=0; ieh_h = h; _EVHTP_HANDLE = eh; /* global */ /* Read config */ diff --git a/apps/restconf/restconf_main_fcgi.c b/apps/restconf/restconf_main_fcgi.c index 67e6d3a7..66cacde3 100644 --- a/apps/restconf/restconf_main_fcgi.c +++ b/apps/restconf/restconf_main_fcgi.c @@ -178,6 +178,7 @@ usage(clicon_handle h, "\t-d \t Specify restconf plugin directory dir (default: %s)\n" "\t-y \t Load yang spec file (override yang main module)\n" "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n" + "\t-u \t Internal socket domain path or IP addr (see -a)\n" "\t-r \t\t Do not drop privileges if run as root\n" "\t-o \"