From 279614d64fad1143ddc03fbf1cf27b19a1bfbfdc Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Fri, 18 Oct 2019 19:33:23 +0200 Subject: [PATCH] * The backend socket has now support of credentials of peer clients * Added: CLICON_NACM_CREDENTIALS and CLICON_NACM_RECOVERY_USER --- CHANGELOG.md | 27 ++- apps/backend/backend_client.c | 69 ++++++- apps/backend/backend_client.h | 18 +- apps/backend/backend_socket.c | 97 ++++----- apps/backend/clixon_backend_handle.c | 4 +- apps/netconf/netconf_hello.c | 6 +- apps/restconf/README.md | 17 +- configure | 70 ++++--- configure.ac | 17 +- include/clixon_config.h.in | 12 +- lib/clixon/clixon_options.h | 16 +- lib/clixon/clixon_uid.h | 2 +- lib/clixon/clixon_xpath.h | 15 +- lib/src/clixon_nacm.c | 1 + lib/src/clixon_options.c | 28 ++- lib/src/clixon_uid.c | 39 +++- lib/src/clixon_xpath.c | 53 ++++- lib/src/clixon_xpath_eval.c | 9 +- lib/src/clixon_xpath_parse.l | 4 +- test/test_identity.sh | 1 - test/test_nacm.sh | 2 + test/test_nacm_credentials.sh | 239 ++++++++++++++++++++++ test/test_nacm_default.sh | 1 + test/test_nacm_ext.sh | 1 + test/test_nacm_module_read.sh | 1 + test/test_nacm_module_write.sh | 1 + test/test_nacm_protocol.sh | 1 + test/test_privileges.sh | 3 +- test/test_sock.sh | 81 ++++++++ util/Makefile.in | 12 ++ util/clixon_util_socket.c | 184 +++++++++++++++++ util/clixon_util_xml.c | 11 +- yang/clixon/clixon-config@2019-09-11.yang | 54 ++++- 33 files changed, 951 insertions(+), 145 deletions(-) create mode 100755 test/test_nacm_credentials.sh create mode 100755 test/test_sock.sh create mode 100644 util/clixon_util_socket.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 300e6af3..de42994c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,14 +3,23 @@ ## 4.2.0 (Expected: October) ### Major New features -* Backend daemon can drop privileges after initialization to run as non-privileged user - * You can start as root and drop privileges either permanently or temporary - * Controlled by options: CLICON_BACKEND_USER and CLICON_BACKEND_PRIVELEGES - * Can also be set with `-U ` clixon_backend command-line option - * If dropped temporary, you can restore privileges with `restore_priv()` +* Privileges and credentials features + * Backend daemon can drop privileges after initialization to run as non-privileged user + * You can start as root and drop privileges either permanently or temporary + * Controlled by options: CLICON_BACKEND_USER and CLICON_BACKEND_PRIVELEGES + * Can also be set with `-U ` clixon_backend command-line option + * If dropped temporary, you can restore privileges with `restore_priv()` + * The backend socket has now support of credentials of peer clients + * NACM users are cross-checked with client credentials (cli/netconf/restconf) + * Only UNIX domain socket supports client credential checks (Not IP sockets. + * Controlled by option CLICON_NACM_CREDENTIALS + * `none` means credentials are not checked. Only option for IP sockets. + * `exact` means credentials of client user must match NACM user exactly. + * `except` means exact match is done except for root and www user.This is necessary for Restconf. This is default. ### API changes on existing features (you may need to change your code) -* In logs and debug changed "Demon error" to "Daemon error". +* NACM users are cross-checked with client user credentials (see new features). +* Changed "Demon error" to "Daemon error" in logs and debug. * Stricter handling of multi-namespace handling * This occurs in cases where there are more than one XML namespaces in a config tree, such as `augment`:ed trees. * Affects all parts of the system, including datastore, backend, restconf and cli. @@ -33,8 +42,10 @@ * Changed so that `400 Bad Request` are for invalid api-path or unknown yang elements, `404 Not Found` for valid xml when object not found. * Typical installation should now add a `clicon` user (as well as group) * New clixon-config@2019-09-11.yang revision - * Added: CLICON_BACKEND_USER: drop of privileges to user, - * Added: CLICON_BACKEND_PRIVELEGES: how to drop privileges + * Added: CLICON_BACKEND_USER: Drop of privileges to this user, owner of backend socket. + * 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_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]` * Replaced JSON `null` with `[null]` as proper empty JSON leaf/leaf-list encoding. diff --git a/apps/backend/backend_client.c b/apps/backend/backend_client.c index 35771988..29d22869 100644 --- a/apps/backend/backend_client.c +++ b/apps/backend/backend_client.c @@ -145,7 +145,6 @@ backend_client_rm(clicon_handle h, } ce_prev = &c->ce_next; } - return backend_client_delete(h, ce); /* actually purge it */ } @@ -1240,6 +1239,68 @@ from_client_ping(clicon_handle h, return 0; } +/*! Verify nacm user with peer uid credentials + * @param[in] mode Peer credential mode: none, exact or except + * @param[in] peername Peer username if any + * @param[in] username username received in XML (eg for NACM) + * @param[out] cbret Set with netconf error message if ret == 0 + * @retval -1 Error + * @retval 0 Not verified (cbret set) + * @retval 1 Verified + * Credentials OK if both NACM user and peer useri s defined AND one of the + * following is true: + * - peer user is same as NACM user + * - peer user is root (can be any NACM user) + * - peer user is www (can be any NACM user) + */ +static int +verify_nacm_user(enum nacm_credentials_t mode, + char *peername, + char *nacmname, + cbuf *cbret) +{ + int retval = -1; + cbuf *cbmsg = NULL; + + if (mode == NC_NONE) + return 1; + if (peername == NULL){ + if (netconf_access_denied(cbret, "application", "No peer user credentials available") < 0) + goto done; + goto fail; + } + if (nacmname == NULL){ + if (netconf_access_denied(cbret, "application", "No NACM available") < 0) + goto done; + goto fail; + } + if (mode == NC_EXCEPT){ + if (strcmp(peername, "root") == 0) + goto ok; + if (strcmp(peername, WWWUSER) == 0) + goto ok; + } + if (strcmp(peername, nacmname) != 0){ + if ((cbmsg = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cbmsg, "User %s credential not matching NACM user %s", peername, nacmname); + if (netconf_access_denied(cbret, "application", cbuf_get(cbmsg)) < 0) + goto done; + goto fail; + } + ok: + retval = 1; + done: + if (cbmsg) + cbuf_free(cbmsg); + return retval; + fail: + retval = 0; + goto done; +} + /*! An internal clicon message has arrived from a client. Receive and dispatch. * @param[in] h Clicon handle * @param[in] s Socket where message arrived. read from this. @@ -1322,6 +1383,12 @@ from_client_msg(clicon_handle h, if ((ret = nacm_access_pre(h, username, NACM_RPC, &xnacm)) < 0) goto done; if (ret == 0){ /* Do NACM validation */ + enum nacm_credentials_t mode; + mode = clicon_nacm_credentials(h); + if ((ret = verify_nacm_user(mode, ce->ce_username, username, cbret)) < 0) + goto done; + if (ret == 0) /* credentials fail */ + goto reply; /* NACM rpc operation exec validation */ if ((ret = nacm_rpc(rpc, module, username, xnacm, cbret)) < 0) goto done; diff --git a/apps/backend/backend_client.h b/apps/backend/backend_client.h index 5aad6843..2ee0b817 100644 --- a/apps/backend/backend_client.h +++ b/apps/backend/backend_client.h @@ -43,15 +43,15 @@ * Keep state about every connected client. */ struct client_entry{ - struct client_entry *ce_next; /* The clients linked list */ - struct sockaddr ce_addr; /* The clients (UNIX domain) address */ - int ce_s; /* stream socket to client */ - int ce_nr; /* Client number (for dbg/tracing) */ - int ce_stat_in; /* Nr of received msgs from client */ - int ce_stat_out;/* Nr of sent msgs to client */ - int ce_pid; /* Process id */ - int ce_uid; /* User id of calling process */ - clicon_handle ce_handle; /* clicon config handle (all clients have same?) */ + struct client_entry *ce_next; /* The clients linked list */ + struct sockaddr ce_addr; /* The clients (UNIX domain) address */ + int ce_s; /* stream socket to client */ + int ce_nr; /* Client number (for dbg/tracing) */ + int ce_stat_in; /* Nr of received msgs from client */ + int ce_stat_out;/* Nr of sent msgs to client */ + int ce_pid; /* Peer Process id */ + char *ce_username;/* Translated from peer user cred */ + clicon_handle ce_handle; /* clicon config handle (all clients have same?) */ }; diff --git a/apps/backend/backend_socket.c b/apps/backend/backend_socket.c index c41b2658..738df455 100644 --- a/apps/backend/backend_socket.c +++ b/apps/backend/backend_socket.c @@ -51,13 +51,12 @@ #include #include #include -#ifdef HAVE_SYS_UCRED_H -#include -#include -#endif #define __USE_GNU /* for ucred */ #define _GNU_SOURCE /* for ucred */ #include +#ifdef HAVE_LOCAL_PEERCRED +#include +#endif #include #include @@ -226,63 +225,65 @@ backend_accept_client(int fd, int retval = -1; clicon_handle h = (clicon_handle)arg; int s; - struct sockaddr_un from; + struct sockaddr from = {0,}; socklen_t len; struct client_entry *ce; -#ifdef DONT_WORK /* XXX HAVE_SYS_UCRED_H */ - struct xucred credentials; /* FreeBSD. */ - socklen_t clen; -#elif defined(SO_PEERCRED) - struct ucred credentials; /* Linux. */ - socklen_t clen; -#endif - char *config_group; - struct group *gr; - char *mem; int i; + char *name = NULL; +#ifdef HAVE_SO_PEERCRED + socklen_t clen; + struct ucred cr = {0,}; /* Linux. */ +#elif defined(HAVE_GETPEEREID) + uid_t euid; + uid_t guid; +#endif clicon_debug(2, "%s", __FUNCTION__); len = sizeof(from); - if ((s = accept(fd, (struct sockaddr*)&from, &len)) < 0){ + if ((s = accept(fd, &from, &len)) < 0){ clicon_err(OE_UNIX, errno, "accept"); goto done; } -#if defined(SO_PEERCRED) - /* fill in the user data structure */ - clen = sizeof(credentials); - if(getsockopt(s, SOL_SOCKET, SO_PEERCRED/* XXX finns ej i freebsd*/, &credentials, &clen)){ - clicon_err(OE_UNIX, errno, "getsockopt"); + if ((ce = backend_client_add(h, &from)) == NULL) goto done; - } -#endif - if ((ce = backend_client_add(h, (struct sockaddr*)&from)) == NULL) - goto done; -#if defined(SO_PEERCRED) - ce->ce_pid = credentials.pid; - ce->ce_uid = credentials.uid; -#endif ce->ce_handle = h; - /* check credentials of caller (not properly implemented yet) */ - if ((config_group = clicon_sock_group(h)) == NULL){ - clicon_err(OE_FATAL, 0, "clicon_sock_group option not set"); - goto done; - } - if ((gr = getgrnam(config_group)) != NULL){ - i = 0; /* one of mem should correspond to ce->ce_uid */ - while ((mem = gr->gr_mem[i++]) != NULL) - ; - } - -#if 0 - { /* XXX */ - int ii; - struct client_entry *c; - for (c = ce_list, ii=0; c; c = c->ce_next, ii++); - clicon_debug(1, "Open client socket (nr:%d pid:%d [Total: %d])", - ce->ce_nr, ce->ce_pid, ii); - } + /* + * Get credentials of connected peer - only for unix socket + */ + switch (from.sa_family){ + case AF_UNIX: +#if defined(HAVE_SO_PEERCRED) + clen = sizeof(cr); + if(getsockopt(s, SOL_SOCKET, SO_PEERCRED, &cr, &clen) < 0){ + clicon_err(OE_UNIX, errno, "getsockopt"); + goto done; + } + ce->ce_pid = cr.pid; /* XXX no use session-id */ + if (uid2name(cr.uid, &name) < 0) + goto done; +#elif defined(HAVE_GETPEEREID) + if (getpeereid(s, &euid, &guid) < 0) + goto done; + if (uid2name(euid, &name) < 0) + goto done; +#else +#error "Need getsockopt O_PEERCRED or getpeereid for unix socket peer cred" #endif + if (name && (ce->ce_username = strdup(name)) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + break; + case AF_INET: /* XXX: HACK ce->ce_pid */ + ce->ce_pid = 0; + for (i=0; ice_pid ^= from.sa_data[i]; + break; + case AF_INET6: + default: + break; + } ce->ce_s = s; /* diff --git a/apps/backend/clixon_backend_handle.c b/apps/backend/clixon_backend_handle.c index 82f5a79f..cd320bd2 100644 --- a/apps/backend/clixon_backend_handle.c +++ b/apps/backend/clixon_backend_handle.c @@ -141,7 +141,7 @@ backend_client_add(clicon_handle h, return NULL; } memset(ce, 0, sizeof(*ce)); - ce->ce_nr = bh->bh_ce_nr++; + ce->ce_nr = bh->bh_ce_nr++; /* Session-id ? */ memcpy(&ce->ce_addr, addr, sizeof(*addr)); ce->ce_next = bh->bh_ce_list; bh->bh_ce_list = ce; @@ -177,6 +177,8 @@ backend_client_delete(clicon_handle h, for (c = *ce_prev; c; c = c->ce_next){ if (c == ce){ *ce_prev = c->ce_next; + if (ce->ce_username) + free(ce->ce_username); free(ce); break; } diff --git a/apps/netconf/netconf_hello.c b/apps/netconf/netconf_hello.c index fc8a620c..882a6aa5 100644 --- a/apps/netconf/netconf_hello.c +++ b/apps/netconf/netconf_hello.c @@ -48,7 +48,6 @@ */ - #ifdef HAVE_CONFIG_H #include "clixon_config.h" /* generated by config & autoconf */ #endif @@ -115,7 +114,10 @@ netconf_hello_dispatch(cxobj *xn) * MUST send a element containing a list of that peer's capabilities * MUST send at least the base NETCONF capability, urn:ietf:params:netconf:base:1.1 * MAY include capabilities for previous NETCONF versions - * MUST include a + * A server MUST include a + * A client MUST NOT include a + * A server receiving MUST terminate the NETCONF session. + * A client not receiving MUST terminate w/o sending * the example shows urn:ietf:params:netconf:capability:startup:1.0 * RFC5277 NETCONF Event Notifications diff --git a/apps/restconf/README.md b/apps/restconf/README.md index bd5d3606..26935464 100644 --- a/apps/restconf/README.md +++ b/apps/restconf/README.md @@ -20,8 +20,12 @@ Download and start nginx. For example on ubuntu: ``` sudo apt install ngnix ``` +on FreeBSD: +``` + sudo pkg install ngnix +``` -Define nginx config file: /etc/nginx/sites-available/default +Edit the nginx config file. (On Ubuntu: `/etc/nginx/sites-available/default`, on FreeBSD: `/usr/local/etc/nginx/sites-available/default`) ``` server { ... @@ -40,6 +44,10 @@ Alternatively, start it via systemd: ``` sudo systemctl start nginx.service ``` +Or on FreeBSD: +``` + sudo service nginx start +``` Start clixon backend daemon (if not already started) ``` @@ -48,8 +56,11 @@ Start clixon backend daemon (if not already started) Start clixon restconf daemon ``` - - sudo su -c "/www-data/clixon_restconf -f /usr/local/etc/example.xml " -s /bin/sh www-data + sudo -u www-data -s /www-data/clixon_restconf -f /usr/local/etc/example.xml +``` +On FreeBSD: +``` + sudo -u www -s /www/clixon_restconf -f /usr/local/etc/example.xml ``` Make restconf calls with curl (or other http client). Example of writing a new interface specification: diff --git a/configure b/configure index 07acdc2f..9774c4fd 100755 --- a/configure +++ b/configure @@ -686,6 +686,7 @@ infodir docdir oldincludedir includedir +runstatedir localstatedir sharedstatedir sysconfdir @@ -768,6 +769,7 @@ datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' +runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' @@ -1020,6 +1022,15 @@ do | -silent | --silent | --silen | --sile | --sil) silent=yes ;; + -runstatedir | --runstatedir | --runstatedi | --runstated \ + | --runstate | --runstat | --runsta | --runst | --runs \ + | --run | --ru | --r) + ac_prev=runstatedir ;; + -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ + | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ + | --run=* | --ru=* | --r=*) + runstatedir=$ac_optarg ;; + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1157,7 +1168,7 @@ fi for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir + libdir localedir mandir runstatedir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1310,6 +1321,7 @@ Fine tuning of the installation directories: --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -1369,8 +1381,8 @@ Some influential environment variables: you have headers in a nonstandard directory CPP C preprocessor YACC The `Yet Another Compiler Compiler' implementation to use. - Defaults to the first program found out of: `bison -o y.tab.c', - `byacc', `yacc'. + Defaults to the first program found out of: `bison -y', `byacc', + `yacc'. YFLAGS The list of arguments that will be passed by default to $YACC. This script will default YFLAGS to the empty string to avoid a default value of `-d' given by some make applications. @@ -3999,7 +4011,7 @@ $as_echo "CFLAGS is $CFLAGS" >&6; } { $as_echo "$as_me:${as_lineno-$LINENO}: result: INSTALLFLAGS is $INSTALLFLAGS" >&5 $as_echo "INSTALLFLAGS is $INSTALLFLAGS" >&6; } -for ac_prog in 'bison -o y.tab.c' byacc +for ac_prog in 'bison -y' byacc do # Extract the first word of "$ac_prog", so it can be a program name with args. set dummy $ac_prog; ac_word=$2 @@ -4715,8 +4727,15 @@ fi if test "${with_wwwuser}"; then wwwuser=${with_wwwuser} + fi -echo "wwwuser:$wwwuser" +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: www user is $wwwuser" >&5 +$as_echo "www user is $wwwuser" >&6; } + +cat >>confdefs.h <<_ACEOF +#define WWWUSER "$wwwuser" +_ACEOF + # Set default config file location CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml @@ -4727,23 +4746,6 @@ if test "${with_configfile+set}" = set; then : fi - -# user credentials for unix sockets -for ac_header in sys/ucred.h -do : - ac_fn_c_check_header_compile "$LINENO" "sys/ucred.h" "ac_cv_header_sys_ucred_h" "# include - -" -if test "x$ac_cv_header_sys_ucred_h" = xyes; then : - cat >>confdefs.h <<_ACEOF -#define HAVE_SYS_UCRED_H 1 -_ACEOF - -fi - -done - - { $as_echo "$as_me:${as_lineno-$LINENO}: checking for socket in -lsocket" >&5 $as_echo_n "checking for socket in -lsocket... " >&6; } if ${ac_cv_lib_socket_socket+:} false; then : @@ -4896,7 +4898,7 @@ fi fi # -for ac_func in inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort +for ac_func in inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort getpeereid do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" @@ -4909,6 +4911,28 @@ fi done +# Checks for getsockopt options for getting unix socket peer credentials on +# Linux +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include +int +main () +{ +getsockopt(1, SOL_SOCKET, SO_PEERCRED, 0, 0); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +$as_echo "#define HAVE_SO_PEERCRED 1" >>confdefs.h + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: Have getsockopt peercred" >&5 +$as_echo "Have getsockopt peercred" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + # YANG_INSTALLDIR is where clixon installs the Clixon yang files # (the files in in yang/clixon) # Each application designer may need to place YANG_INSTALLDIR in their config: diff --git a/configure.ac b/configure.ac index d4dff867..eff14823 100644 --- a/configure.ac +++ b/configure.ac @@ -198,8 +198,10 @@ AC_ARG_WITH([wwwuser], [AS_HELP_STRING([--with-wwwuser=],[Set www user different from www-data])]) if test "${with_wwwuser}"; then wwwuser=${with_wwwuser} + fi -echo "wwwuser:$wwwuser" +AC_MSG_RESULT(www user is $wwwuser) +AC_DEFINE_UNQUOTED(WWWUSER, "$wwwuser", [WWW user for restconf daemon]) # Set default config file location CLIXON_DEFAULT_CONFIG=/usr/local/etc/clixon.xml @@ -207,12 +209,6 @@ AC_ARG_WITH([configfile], [AS_HELP_STRING([--with-configfile=FILE],[set default path to config file])], [CLIXON_DEFAULT_CONFIG="$withval"],) - -# user credentials for unix sockets -AC_CHECK_HEADERS([sys/ucred.h],[],[], - [[# include ]] -) - AC_CHECK_LIB(socket, socket) AC_CHECK_LIB(dl, dlopen) @@ -226,7 +222,12 @@ if test "${with_libxml2}"; then fi # -AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort) +AC_CHECK_FUNCS(inet_aton sigaction sigvec strlcpy strsep strndup alphasort versionsort getpeereid) + +# Checks for getsockopt options for getting unix socket peer credentials on +# Linux +AC_TRY_COMPILE([#include ], [getsockopt(1, SOL_SOCKET, SO_PEERCRED, 0, 0);], [AC_DEFINE(HAVE_SO_PEERCRED, 1, [Have getsockopt peercred]) +AC_MSG_RESULT(Have getsockopt peercred)]) # YANG_INSTALLDIR is where clixon installs the Clixon yang files # (the files in in yang/clixon) diff --git a/include/clixon_config.h.in b/include/clixon_config.h.in index 08f448f2..8db6aade 100644 --- a/include/clixon_config.h.in +++ b/include/clixon_config.h.in @@ -24,6 +24,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_CLIGEN_CLIGEN_H +/* Define to 1 if you have the `getpeereid' function. */ +#undef HAVE_GETPEEREID + /* Define to 1 if you have the `inet_aton' function. */ #undef HAVE_INET_ATON @@ -60,6 +63,9 @@ /* Define to 1 if you have the `sigvec' function. */ #undef HAVE_SIGVEC +/* Have getsockopt peercred */ +#undef HAVE_SO_PEERCRED + /* Define to 1 if you have the header file. */ #undef HAVE_STDINT_H @@ -87,9 +93,6 @@ /* Define to 1 if you have the header file. */ #undef HAVE_SYS_TYPES_H -/* Define to 1 if you have the header file. */ -#undef HAVE_SYS_UCRED_H - /* Define to 1 if you have the header file. */ #undef HAVE_UNISTD_H @@ -117,6 +120,9 @@ /* Define to 1 if you have the ANSI C header files. */ #undef STDC_HEADERS +/* WWW user for restconf daemon */ +#undef WWWUSER + /* Define to 1 if `lex' declares `yytext' as a `char *' by default, not a `char[]'. */ #undef YYTEXT_POINTER diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index 04fd6a61..fdca3460 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -77,7 +77,14 @@ enum startup_mode_t{ enum priv_mode_t{ PM_NONE=0, /* Make no drop/change in privileges */ PM_DROP_PERM, /* Drop privileges permanently */ - PM_DROP_TEMP, /* Drop privileges temporary */ + PM_DROP_TEMP /* Drop privileges temporary */ +}; + +/*! See clixon-config.yang type nacm_cred_mode (user credentials) */ +enum nacm_credentials_t{ + NC_NONE=0, /* "Dont match NACM user to any user credentials. */ + NC_EXACT, /* Exact match between NACM user and unix socket peer user. */ + NC_EXCEPT /* Exact match except for root and www user */ }; /*! Datastore cache behaviour, see clixon_datastore.[ch] @@ -183,6 +190,9 @@ static inline char *clicon_backend_pidfile(clicon_handle h){ static inline char *clicon_xmldb_dir(clicon_handle h){ return clicon_option_str(h, "CLICON_XMLDB_DIR"); } +static inline char *clicon_nacm_recovery_user(clicon_handle h){ + return clicon_option_str(h, "CLICON_NACM_RECOVERY_USER"); +} /*-- Specific option access functions for YANG options w type conversion--*/ int clicon_cli_genmodel(clicon_handle h); @@ -193,7 +203,9 @@ int clicon_sock_family(clicon_handle h); int clicon_sock_port(clicon_handle h); int clicon_autocommit(clicon_handle h); int clicon_startup_mode(clicon_handle h); -int clicon_backend_privileges_mode(clicon_handle h); +enum priv_mode_t clicon_backend_privileges_mode(clicon_handle h); +enum nacm_credentials_t clicon_nacm_credentials(clicon_handle h); + enum datastore_cache clicon_datastore_cache(clicon_handle h); enum regexp_mode clicon_yang_regexp(clicon_handle h); /*-- Specific option access functions for non-yang options --*/ diff --git a/lib/clixon/clixon_uid.h b/lib/clixon/clixon_uid.h index 8c5bd902..21ca842e 100644 --- a/lib/clixon/clixon_uid.h +++ b/lib/clixon/clixon_uid.h @@ -41,8 +41,8 @@ * Prototypes */ int group_name2gid(const char *name, gid_t *gid); - int name2uid(const char *name, uid_t *uid); +int uid2name(const uid_t uid, char **name); int drop_priv_temp(uid_t new_uid); int drop_priv_perm(uid_t new_uid); int restore_priv(void); diff --git a/lib/clixon/clixon_xpath.h b/lib/clixon/clixon_xpath.h index ff228042..0998da16 100644 --- a/lib/clixon/clixon_xpath.h +++ b/lib/clixon/clixon_xpath.h @@ -56,7 +56,9 @@ enum xp_op{ XO_UNION, }; -/* Axis specifiers according to https://www.w3.org/TR/xpath-10/#NT-AxisName */ +/* Axis specifiers according to https://www.w3.org/TR/xpath-10/#NT-AxisName + * @see axis_type_int2str + */ enum axis_type{ A_NAN = 0, /* Not set */ A_ANCESTOR, @@ -69,13 +71,15 @@ enum axis_type{ A_FOLLOWING_SIBLING, A_NAMESPACE, A_PARENT, - A_PRECEEDING, - A_PRECEEDING_SIBLING, + A_PRECEDING, + A_PRECEDING_SIBLING, A_SELF, A_ROOT /* XXX Not in https://www.w3.org/TR/xpath-10 */ }; -/* used as non-terminal type in yacc rules */ +/* used as non-terminal type in yacc rules + * @see xpath_tree_int2str + */ enum xp_type{ XP_EXP, XP_AND, @@ -100,7 +104,7 @@ enum xp_type{ */ struct xpath_tree{ enum xp_type xs_type; - int xs_int; + int xs_int; /* step-> axis-type */ double xs_double; char *xs_s0; char *xs_s1; @@ -112,6 +116,7 @@ typedef struct xpath_tree xpath_tree; /* * Prototypes */ +char* axis_type_int2str(int axis_type); char* xpath_tree_int2str(int nodetype); int xpath_tree_print_cb(cbuf *cb, xpath_tree *xs); int xpath_tree_print(FILE *f, xpath_tree *xs); diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c index 9af77114..8ba668a7 100644 --- a/lib/src/clixon_nacm.c +++ b/lib/src/clixon_nacm.c @@ -942,6 +942,7 @@ nacm_access_pre(clicon_handle h, if (xml_rootchild_node(xnacm0, xnacm) < 0) goto done; xnacm0 = NULL; + /* Initial NACM steps and common to all NACM access validation. */ if ((retval = nacm_access(mode, xnacm, username)) < 0) goto done; if (retval == 0){ /* if retval == 0 then return an xml nacm tree */ diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 5ebe3c83..42b8326f 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -101,7 +101,7 @@ static const map_str2int startup_mode_map[] = { {NULL, -1} }; -/* Mapping between Clicon privilegese modes string <--> constants, +/* Mapping between Clicon privileges modes string <--> constants, * see clixon-config.yang type priv_mode */ static const map_str2int priv_mode_map[] = { {"none", PM_NONE}, @@ -110,6 +110,16 @@ static const map_str2int priv_mode_map[] = { {NULL, -1} }; + +/* Mapping between Clicon nacm user credential string <--> constants, + * see clixon-config.yang type nacm_cred_mode */ +static const map_str2int nacm_credentials_map[] = { + {"none", NC_NONE}, + {"exact", NC_EXACT}, + {"except", NC_EXCEPT}, + {NULL, -1} +}; + /* Mapping between datastore cache string <--> constants, * see clixon-config.yang type datastore_cache */ static const map_str2int datastore_cache_map[] = { @@ -706,7 +716,7 @@ clicon_startup_mode(clicon_handle h) * @param[in] h Clicon handle * @retval mode Privileges mode */ -int +enum priv_mode_t clicon_backend_privileges_mode(clicon_handle h) { char *mode; @@ -716,6 +726,20 @@ clicon_backend_privileges_mode(clicon_handle h) return clicon_str2int(priv_mode_map, mode); } +/*! Which privileges drop method to use + * @param[in] h Clicon handle + * @retval mode Privileges mode + */ +enum nacm_credentials_t +clicon_nacm_credentials(clicon_handle h) +{ + char *mode; + + if ((mode = clicon_option_str(h, "CLICON_NACM_CREDENTIALS")) == NULL) + return -1; + return clicon_str2int(nacm_credentials_map, mode); +} + /*! Which datastore cache method to use * @param[in] h Clicon handle * @retval method Datastore cache method diff --git a/lib/src/clixon_uid.c b/lib/src/clixon_uid.c index 515f0988..b08cef5b 100644 --- a/lib/src/clixon_uid.c +++ b/lib/src/clixon_uid.c @@ -90,10 +90,10 @@ group_name2gid(const char *name, } /*! Translate user name to uid. Return -1 if error or not found. - * @param[in] name Name of user - * @param[out] uid User id - * @retval 0 OK - * @retval -1 Error. or not found + * @param[in] name Name of user + * @param[out] uid User id + * @retval 0 OK + * @retval -1 Error. or not found */ int name2uid(const char *name, @@ -119,6 +119,37 @@ name2uid(const char *name, return retval; } +/*! Translate uid to user name + * @param[in] uid User id + * @param[out] name User name + * @retval 0 OK + * @retval -1 Error. or not found + */ +int +uid2name(const uid_t uid, + char **name) +{ + int retval = -1; + char buf[1024]; + struct passwd pwbuf; + struct passwd *pwbufp = NULL; + + if (getpwuid_r(uid, &pwbuf, buf, sizeof(buf), &pwbufp) != 0){ + clicon_err(OE_UNIX, errno, "getpwuid_r(%u)", uid); + goto done; + } + if (pwbufp == NULL){ + clicon_err(OE_UNIX, 0, "No such user: %u", uid); + goto done; + } + if (name) + *name = pwbufp->pw_name; + retval = 0; + done: + return retval; +} + + /* Privileges drop perm, temp and restore * @see https://www.usenix.org/legacy/events/sec02/full_papers/chen/chen.pdf */ diff --git a/lib/src/clixon_xpath.c b/lib/src/clixon_xpath.c index b5f2ee1a..3e7a7056 100644 --- a/lib/src/clixon_xpath.c +++ b/lib/src/clixon_xpath.c @@ -93,7 +93,9 @@ * Variables */ -/* Mapping between xpath_tree node name string <--> int */ +/* Mapping between xpath_tree node name string <--> int + * @see xpath_tree_int2str + */ static const map_str2int xpath_tree_map[] = { {"expr", XP_EXP}, {"andexpr", XP_AND}, @@ -115,11 +117,42 @@ static const map_str2int xpath_tree_map[] = { {NULL, -1} }; +/* Mapping between axis_type string <--> int + * @see axis_type_int2str + */ +static const map_str2int axis_type_map[] = { + {"NaN", A_NAN}, + {"ancestor", A_ANCESTOR}, + {"ancestor-or-selgf", A_ANCESTOR_OR_SELF}, + {"attribute", A_ATTRIBUTE}, + {"child", A_CHILD}, + {"descendant", A_DESCENDANT}, + {"descendeant-or-self", A_DESCENDANT_OR_SELF}, + {"following", A_FOLLOWING}, + {"following-sibling", A_FOLLOWING_SIBLING}, + {"namespace", A_NAMESPACE}, + {"parent", A_PARENT}, + {"preceding", A_PRECEDING}, + {"preceding-sibling", A_PRECEDING_SIBLING}, + {"self", A_SELF}, + {"root", A_ROOT}, + {NULL, -1} +}; + /* * XPATH parse tree type */ +/*! Map from axis-type int to string + */ +char* +axis_type_int2str(int axis_type) +{ + return (char*)clicon_int2str(axis_type_map, axis_type); +} + + /*! Map from xpath_tree node name int to string */ char* @@ -135,11 +168,21 @@ xpath_tree_print0(cbuf *cb, int level) { cprintf(cb, "%*s%s:", level*3, "", xpath_tree_int2str(xs->xs_type)); - if (xs->xs_s0){ + if (xs->xs_s0) cprintf(cb, "\"%s\" ", xs->xs_s0); - if (xs->xs_s1) - cprintf(cb,"\"%s\" ", xs->xs_s1); - } + if (xs->xs_s1) + cprintf(cb,"\"%s\" ", xs->xs_s1); + if (xs->xs_int) + switch (xs->xs_type){ + case XP_STEP: + cprintf(cb, "%s", axis_type_int2str(xs->xs_int)); + break; + default: + cprintf(cb, "%d ", xs->xs_int); + break; + } + if (xs->xs_double) + cprintf(cb,"%lf ", xs->xs_double); cprintf(cb, "\n"); if (xs->xs_c0) xpath_tree_print0(cb, xs->xs_c0,level+1); diff --git a/lib/src/clixon_xpath_eval.c b/lib/src/clixon_xpath_eval.c index 2bc21d9e..d6b0eb7b 100644 --- a/lib/src/clixon_xpath_eval.c +++ b/lib/src/clixon_xpath_eval.c @@ -167,7 +167,7 @@ nodetest_eval_node(cxobj *x, name2, nsxpath); } #endif - done: /* retval set in preceeding statement */ + done: /* retval set in preceding statement */ return retval; } @@ -303,9 +303,10 @@ xp_eval_step(xp_ctx *xc0, x = NULL; while ((x = xml_child_each(xv, x, CX_ELMNT)) != NULL) { /* xs->xs_c0 is nodetest */ - if (nodetest == NULL || nodetest_eval(x, nodetest, nsc) == 1) + if (nodetest == NULL || nodetest_eval(x, nodetest, nsc) == 1){ if (cxvec_append(x, &vec, &veclen) < 0) goto done; + } } } } @@ -342,9 +343,9 @@ xp_eval_step(xp_ctx *xc0, vec = NULL; } break; - case A_PRECEEDING: + case A_PRECEDING: break; - case A_PRECEEDING_SIBLING: + case A_PRECEDING_SIBLING: break; case A_SELF: break; diff --git a/lib/src/clixon_xpath_parse.l b/lib/src/clixon_xpath_parse.l index 180adc9c..31699443 100644 --- a/lib/src/clixon_xpath_parse.l +++ b/lib/src/clixon_xpath_parse.l @@ -128,8 +128,8 @@ real ({digit}+[.]{digit}*)|({digit}*[.]{digit}+) following-sibling:: { clixon_xpath_parselval.intval = A_FOLLOWING_SIBLING; return AXISNAME; } namespace:: { clixon_xpath_parselval.intval = A_NAMESPACE; return AXISNAME; } parent:: { clixon_xpath_parselval.intval = A_PARENT; return AXISNAME; } -preceding:: { clixon_xpath_parselval.intval = A_PRECEEDING; return AXISNAME; } -preceding-sibling:: { clixon_xpath_parselval.intval = A_PRECEEDING_SIBLING; return AXISNAME; } +preceding:: { clixon_xpath_parselval.intval = A_PRECEDING; return AXISNAME; } +preceding-sibling:: { clixon_xpath_parselval.intval = A_PRECEDING_SIBLING; return AXISNAME; } self:: { clixon_xpath_parselval.intval = A_SELF; return AXISNAME; } current { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; } diff --git a/test/test_identity.sh b/test/test_identity.sh index 631e4a35..25e91781 100755 --- a/test/test_identity.sh +++ b/test/test_identity.sh @@ -27,7 +27,6 @@ cat < $cfg $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile - 1 /usr/local/var/$APPNAME EOF diff --git a/test/test_nacm.sh b/test/test_nacm.sh index a7bacde9..69dcfc5b 100755 --- a/test/test_nacm.sh +++ b/test/test_nacm.sh @@ -2,6 +2,7 @@ # Authentication and authorization and IETF NACM # See RFC 8341 A.2 # But replaced ietf-netconf-monitoring with * +# Note credenials check set to none since USER poses as different users. # Magic line must be first in script (see README.md) s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi @@ -31,6 +32,7 @@ cat < $cfg /usr/local/var/$APPNAME false internal + none EOF diff --git a/test/test_nacm_credentials.sh b/test/test_nacm_credentials.sh new file mode 100755 index 00000000..e61b803a --- /dev/null +++ b/test/test_nacm_credentials.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash +# Test matrching of Unix peer credentials with NACM users +# Use raw unix socket instead of clients (cli/netconf/restconf) since they do +# magic things with the username and here it needs to be handled explicitly. +# test matrix: +# - mode: none, exact, except +# - username: olof, admin, null, sudo +# - socket family: unix|ip + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +# Raw unit tester of backend unix socket +: ${clixon_util_socket:=clixon_util_socket} + +APPNAME=example + +# Common NACM scripts +. ./nacm.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/nacm-example.yang + +cat < $fyang +module nacm-example{ + yang-version 1.1; + namespace "urn:example:nacm"; + prefix nacm; + import clixon-example { + prefix ex; + } + import ietf-netconf-acm { + prefix nacm; + } + leaf x{ + type int32; + description "something to edit"; + } +} +EOF + +# The groups are slightly modified from RFC8341 A.1 +# The rule-list is from A.2 +RULES=$(cat < + false + permit + deny + deny + + $NGROUPS + + + guest-acl + guest + + deny-ncm + * + * + deny + + Do not allow guests any access to the NETCONF + monitoring information. + + + + + limited-acl + limited + + permit-get + get + * + exec + permit + + Allow get + + + + permit-get-config + get-config + * + exec + permit + + Allow get-config + + + + + $NADMIN + + + 0 +EOF +) + +# Set cred mode and run nacm operations +# Arguments: +# - mode (none,exact,except) +# - xml/nacm-username +# - socket family +# - socket file/addr +# - precommand /(eg sudo to raise to root) +testrun(){ + mode=$1 + username=$2 + family=$3 + sock=$4 + exp=$5 + precmd=$6 + +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $fyang + $family + $sock + /usr/local/lib/$APPNAME/backend + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + internal + $mode + +EOF + + new "test params: -f $cfg" + if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg + fi + + new "waiting" + wait_backend + + # First push in nacm rules via regular means + new "auth set authentication config" +expecteof "$clixon_netconf -qf $cfg" 0 "$RULES]]>]]>" "^]]>]]>$" + + new "enable nacm" + expecteof "$clixon_netconf -qf $cfg" 0 'true]]>]]>' "^]]>]]>$" + + new "commit it" + expecteof "$clixon_netconf -qf $cfg" 0 "]]>]]>" "^]]>]]>$" + + # raw socket test + if [ -n "$username" ]; then + XML="" + else + XML="" + fi + + new "get-config mode:$mode user:$username $family $precmd" + expecteof "$precmd $clixon_util_socket -a $family -s $sock -D $DBG" 0 "$XML" "$ex" + + if [ $BE -ne 0 ]; then # Bring your own backend + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi +} # testrun + +OK='^0$' +ERROR='^applicationaccess-deniederror' + +# UNIX socket, no user, loop mode. All fail since null user cant access anything +if true; then +new "Credentials: mode=none, fam=UNIX user=none" +testrun none "" UNIX $dir/backend.sock "$OK" "" + +new "Credentials: mode=exact, fam=UNIX user=none" +testrun exact "" UNIX $dir/backend.sock "$OK" "" + +new "Credentials: mode=except, fam=UNIX user=none" +testrun except "" UNIX $dir/backend.sock "$OK" "" +fi + +# UNIX socket, myuser, loop mode. All should work +if true; then +new "Credentials: mode=none, fam=UNIX user=me" +testrun none "$USER" UNIX $dir/backend.sock "$OK" "" + +new "Credentials: mode=exact, fam=UNIX user=me" +testrun exact "$USER" UNIX $dir/backend.sock "$OK" "" + +new "Credentials: mode=except, fam=UNIX user=me" +testrun except "$USER" UNIX $dir/backend.sock "$OK" "" +fi + +# UNIX socket, admin user. First should work +if true; then +new "Credentials: mode=none, fam=UNIX user=admin" +testrun none admin UNIX $dir/backend.sock "$OK" "" + +new "Credentials: mode=exact, fam=UNIX user=admin" +testrun exact admin UNIX $dir/backend.sock "$ERROR" "" + +new "Credentials: mode=except, fam=UNIX user=admin" +testrun except admin UNIX $dir/backend.sock "$ERROR" "" +fi + +# UNIX socket, admin user. sudo self to root. First and last should work +if true; then +new "Credentials: mode=none, fam=UNIX user=admin sudo" +testrun none admin UNIX $dir/backend.sock "$OK" sudo + +new "Credentials: mode=exact, fam=UNIX user=admin sudo" +testrun exact admin UNIX $dir/backend.sock "$ERROR" sudo + +new "Credentials: mode=except, fam=UNIX user=admin sudo" +testrun except admin UNIX $dir/backend.sock "$OK" sudo +fi + +# IPv4 socket, admin user. First should work +if true; then +new "Credentials: mode=none, fam=UNIX user=admin sudo" +testrun none $USER IPv4 127.0.0.1 "$OK" "" + +new "Credentials: mode=exact, fam=UNIX user=admin sudo" +testrun exact $USER IPv4 127.0.0.1 "$ERROR" "" + +new "Credentials: mode=except, fam=UNIX user=admin sudo" +testrun except $USER IPv4 127.0.0.1 "$ERROR" "" +fi + +rm -rf $dir diff --git a/test/test_nacm_default.sh b/test/test_nacm_default.sh index f2bedf80..1ee6cd54 100755 --- a/test/test_nacm_default.sh +++ b/test/test_nacm_default.sh @@ -32,6 +32,7 @@ cat < $cfg /usr/local/lib/xmldb/text.so false internal + none $format EOF diff --git a/test/test_nacm_ext.sh b/test/test_nacm_ext.sh index 2501796d..2abb1640 100755 --- a/test/test_nacm_ext.sh +++ b/test/test_nacm_ext.sh @@ -36,6 +36,7 @@ cat < $cfg false external $nacmfile + none EOF diff --git a/test/test_nacm_module_read.sh b/test/test_nacm_module_read.sh index 2665f805..e4459917 100755 --- a/test/test_nacm_module_read.sh +++ b/test/test_nacm_module_read.sh @@ -46,6 +46,7 @@ cat < $cfg /usr/local/var/$APPNAME false internal + none EOF diff --git a/test/test_nacm_module_write.sh b/test/test_nacm_module_write.sh index 06804480..7b1d20d6 100755 --- a/test/test_nacm_module_write.sh +++ b/test/test_nacm_module_write.sh @@ -48,6 +48,7 @@ cat < $cfg /usr/local/var/$APPNAME false internal + none EOF diff --git a/test/test_nacm_protocol.sh b/test/test_nacm_protocol.sh index 0666329f..67bd182c 100755 --- a/test/test_nacm_protocol.sh +++ b/test/test_nacm_protocol.sh @@ -52,6 +52,7 @@ cat < $cfg /usr/local/var/$APPNAME false internal + none EOF diff --git a/test/test_privileges.sh b/test/test_privileges.sh index d22a2732..fa5358b7 100755 --- a/test/test_privileges.sh +++ b/test/test_privileges.sh @@ -13,7 +13,8 @@ APPNAME=example cfg=$dir/conf_startup.xml if [ $valgrindtest -ne 0 ]; then - return -1 # skip + echo "...skipped " + return 0 # skip fi # Here $dir is created by the user that runs the script diff --git a/test/test_sock.sh b/test/test_sock.sh new file mode 100755 index 00000000..a2e62054 --- /dev/null +++ b/test/test_sock.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash +# Simple Unix and IP internal socket tests +# See also test_nacm_credentials.sh for more advanced credential tests + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +# +# client <---> backend +# ^ is unix, ipv4, ipv6 socket + +APPNAME=example + +cfg=$dir/conf.xml +fyang=$dir/socket.yang + +# Set socket family and start backend and run a single cli command to +# check socket works +# 1: UNIX|IPv4|IPv6 +# 2: unix file or ipv4 address or ipv6 address +# 3: sock port (if ipv4 or ipv6) +testrun(){ + family=$1 + sock=$2 + port=$3 + +cat < $cfg + + $cfg + *:* + /usr/local/share/clixon + clixon-example + $family + $port + $sock + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.pidfile + /usr/local/var/$APPNAME + init + false + +EOF + + new "test params: -f $cfg" + if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s init -f $cfg" + start_backend -s init -f $cfg + fi + + new "$clixon_cli -1f $cfg show version" + expectfn "$clixon_cli -1f $cfg show version" 0 "$version." + + if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg + fi +} + +new "Unix socket" +testrun UNIX $dir/sock 0 + +new "IPv4 socket" +testrun IPv4 127.0.0.1 7878 + +#new "IPv6 socket" NYI +#testrun IPv6 ::1 7878 + +rm -rf $dir diff --git a/util/Makefile.in b/util/Makefile.in index f8422df5..8eb45272 100644 --- a/util/Makefile.in +++ b/util/Makefile.in @@ -78,6 +78,9 @@ APPSRC += clixon_util_regexp.c ifeq ($(with_restconf),yes) APPSRC += clixon_util_stream.c # Needs curl endif +APPSRC += clixon_util_socket.c +APPSRC += clixon_util_ssl.c +APPSRC += clixon_util_grpc.c APPS = $(APPSRC:.c=) @@ -117,6 +120,15 @@ clixon_util_stream: clixon_util_stream.c $(LIBDEPS) $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lcurl -o $@ endif +clixon_util_socket: clixon_util_socket.c $(LIBDEPS) + $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -o $@ + +clixon_util_ssl: clixon_util_ssl.c $(LIBDEPS) + $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lnghttp2 -lssl -lcrypto -o $@ + +clixon_util_grpc: clixon_util_grpc.c $(LIBDEPS) + $(CC) $(INCLUDES) $(CPPFLAGS) @CFLAGS@ $(LDFLAGS) $^ $(LIBS) -lnghttp2 -lssl -lcrypto -o $@ + distclean: clean rm -f Makefile *~ .depend diff --git a/util/clixon_util_socket.c b/util/clixon_util_socket.c new file mode 100644 index 00000000..6c1cbfcf --- /dev/null +++ b/util/clixon_util_socket.c @@ -0,0 +1,184 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren + + 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 ***** + + * Unit test for testting the backend socket, ie simulating a client by + * directly sending XML to the backend. + * Precondition: + * The backend must have been started using socket path goven as -s + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clixon */ +#include "clixon/clixon.h" + +static int +usage(char *argv0) +{ + fprintf(stderr, "usage:%s [options] with xml on stdin (unless -f)\n" + "where options are\n" + "\t-h \t\tHelp\n" + "\t-D \tDebug\n" + "\t-a \tSocket address family (default UNIX)\n" + "\t-s \tPath to unix domain socket (or IP addr)\n" + "\t-f \tXML input file (overrides stdin)\n" + "\t-J \t\tInput as JSON (instead of XML)\n" + , + argv0); + exit(0); +} + +int +main(int argc, + char **argv) +{ + int retval = -1; + int c; + int logdst = CLICON_LOG_STDERR; + struct clicon_msg *msg = NULL; + char *sockpath; + char *retdata = NULL; + int jsonin = 0; + char *input_filename = NULL; + int fd = 0; /* stdin */ + cxobj *xt = NULL; + cxobj *xc; + cxobj *xerr = NULL; + char *family = "UNIX"; + int ret; + cbuf *cb = cbuf_new(); + + /* In the startup, logs to stderr & debug flag set later */ + clicon_log_init(__FILE__, LOG_INFO, CLICON_LOG_STDERR); + + optind = 1; + opterr = 0; + while ((c = getopt(argc, argv, "hD:s:f:Ja:")) != -1) + switch (c) { + case 'h': + usage(argv[0]); + break; + case 'D': + if (sscanf(optarg, "%d", &debug) != 1) + usage(argv[0]); + break; + case 's': + sockpath = optarg; + break; + case 'f': + input_filename = optarg; + break; + case 'J': + jsonin++; + break; + case 'a': + family = optarg; + break; + default: + usage(argv[0]); + break; + } + clicon_log_init(__FILE__, debug?LOG_DEBUG:LOG_INFO, logdst); + if (sockpath == NULL){ + fprintf(stderr, "Mandatory option missing: -s \n"); + usage(argv[0]); + } + if (input_filename){ + if ((fd = open(input_filename, O_RDONLY)) < 0){ + clicon_err(OE_YANG, errno, "open(%s)", input_filename); + goto done; + } + } + /* 2. Parse data (xml/json) */ + if (jsonin){ + if ((ret = json_parse_file(fd, NULL, &xt, &xerr)) < 0) + goto done; + if (ret == 0){ + fprintf(stderr, "Invalid JSON\n"); + goto done; + } + } + else{ + if (xml_parse_file(fd, NULL, NULL, &xt) < 0){ + fprintf(stderr, "xml parse error: %s\n", clicon_err_reason); + goto done; + } + } + if ((xc = xml_child_i(xt, 0)) == NULL){ + fprintf(stderr, "No xml\n"); + goto done; + } + if (clicon_xml2cbuf(cb, xc, 0, 0, -1) < 0) + goto done; + if ((msg = clicon_msg_encode("%s", cbuf_get(cb))) < 0) + goto done; + if (strcmp(family, "UNIX")==0){ + if (clicon_rpc_connect_unix(msg, sockpath, &retdata, NULL) < 0) + goto done; + } + else + if (clicon_rpc_connect_inet(msg, sockpath, 4535, &retdata, NULL) < 0) + goto done; + fprintf(stdout, "%s\n", retdata); + retval = 0; + done: + if (xerr) + xml_free(xerr); + if (xt) + xml_free(xt); + if (msg) + free(msg); + if (cb) + cbuf_free(cb); + return retval; +} + + diff --git a/util/clixon_util_xml.c b/util/clixon_util_xml.c index ea38cf23..45a1b0e7 100644 --- a/util/clixon_util_xml.c +++ b/util/clixon_util_xml.c @@ -64,19 +64,10 @@ /* clixon */ #include "clixon/clixon.h" -/* - * Turn this on to get a xml parse and pretty print test program - * Usage: xpath - * read xml from input - * Example compile: - gcc -g -o xml -I. -I../clixon ./clixon_xml.c -lclixon -lcligen - * Example run: - echo "" | xml -*/ static int usage(char *argv0) { - fprintf(stderr, "usage:%s [options] with xml on stdin\n" + fprintf(stderr, "usage:%s [options] with xml on stdin (unless -f)\n" "where options are\n" "\t-h \t\tHelp\n" "\t-D \tDebug\n" diff --git a/yang/clixon/clixon-config@2019-09-11.yang b/yang/clixon/clixon-config@2019-09-11.yang index 534b7a6b..dc014559 100644 --- a/yang/clixon/clixon-config@2019-09-11.yang +++ b/yang/clixon/clixon-config@2019-09-11.yang @@ -42,7 +42,9 @@ module clixon-config { revision 2019-09-11 { description "Added: CLICON_BACKEND_USER: drop of privileges to user, - CLICON_BACKEND_PRIVILEGES: how to drop privileges"; + CLICON_BACKEND_PRIVILEGES: how to drop privileges + CLICON_NACM_CREDENTIALS: If and how to check backend sock priveleges with NACM + CLICON_NACM_RECOVERY_USER: Name of NACM recovery user."; } revision 2019-06-05 { description @@ -203,6 +205,29 @@ module clixon-config { } } } + typedef nacm_cred_mode{ + description + "How NACM user should be matched with unix socket peer credentials. + This means nacm user must match socket peer user accessing the + backend socket. For IP sockets only mode none makes sense."; + type enumeration{ + enum none { + description + "Dont match NACM user to any user credentials. Any user can pose + as any other user. Set this for IP sockets, or dont use NACM."; + } + enum exact { + description + "Exact match between NACM user and unix socket peer user. + Except for root user that can pose as any user."; + } + enum except { + description + "Exact match between NACM user and unix socket peer user, except + for root and www user (restconf)."; + } + } + } container clixon-config { leaf-list CLICON_FEATURE { @@ -421,7 +446,12 @@ module clixon-config { default "UNIX"; description "Address family for communicating with clixon_backend - (UNIX|IPv4|IPv6)"; + (UNIX|IPv4). IPv6 not yet implemented. + Note that UNIX socket makes credential check as follows: + (1) client needs rw access to the socket + (2) NACM credentials can be checked according to CLICON_NACM_CREDENTIALS + Warning: IPv4 and IPv6 sockets have no credential mechanism. + "; } leaf CLICON_SOCK { type string; @@ -543,6 +573,26 @@ module clixon-config { type string; description "RFC8341 NACM external configuration file"; } + leaf CLICON_NACM_CREDENTIALS { + type nacm_cred_mode; + default except; + description + "Verify nacm user credentials with unix socket peer cred. + This means nacm user must match unix user accessing the backend + socket. + Except for recovery user and www user (for restconf)"; + } + leaf CLICON_NACM_RECOVERY_USER { + type string; + default "_nacm_recovery"; + description + "C8341 defines a 'recovery session' as outside the scope. Clixon + defines this user as having special admin rights to exempt from + all access control enforcements. + Note setting of CLICON_NACM_CREDENTIALS is important, if set to + exact for example, this user must exist and be used, otherwise + another user (such as root or www) can pose as it."; + } leaf CLICON_MODULE_LIBRARY_RFC7895 { type boolean; default true;