* The backend socket has now support of credentials of peer clients

* Added: CLICON_NACM_CREDENTIALS and CLICON_NACM_RECOVERY_USER
This commit is contained in:
Olof hagsand 2019-10-18 19:33:23 +02:00
parent 77b491c568
commit 279614d64f
33 changed files with 951 additions and 145 deletions

View file

@ -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 <user>` 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 <user>` 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.

View file

@ -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;

View file

@ -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?) */
};

View file

@ -51,13 +51,12 @@
#include <sys/time.h>
#include <sys/stat.h>
#include <sys/param.h>
#ifdef HAVE_SYS_UCRED_H
#include <sys/types.h>
#include <sys/ucred.h>
#endif
#define __USE_GNU /* for ucred */
#define _GNU_SOURCE /* for ucred */
#include <sys/socket.h>
#ifdef HAVE_LOCAL_PEERCRED
#include <sys/ucred.h>
#endif
#include <sys/param.h>
#include <sys/types.h>
@ -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; i<len; i++)
ce->ce_pid ^= from.sa_data[i];
break;
case AF_INET6:
default:
break;
}
ce->ce_s = s;
/*

View file

@ -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;
}

View file

@ -48,7 +48,6 @@
</hello>
*/
#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 <hello> 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 <session-id>
* A server MUST include a <session-id>
* A client MUST NOT include a <session-id>
* A server receiving <session-id> MUST terminate the NETCONF session.
* A client not receiving <session-id> MUST terminate w/o sending<close-session>
* the example shows urn:ietf:params:netconf:capability:startup:1.0
* RFC5277 NETCONF Event Notifications

View file

@ -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:

70
configure vendored
View file

@ -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 <include dir>
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 <sys/param.h>
"
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 <sys/socket.h>
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:

View file

@ -198,8 +198,10 @@ AC_ARG_WITH([wwwuser],
[AS_HELP_STRING([--with-wwwuser=<user>],[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 <sys/param.h>]]
)
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 <sys/socket.h>], [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)

View file

@ -24,6 +24,9 @@
/* Define to 1 if you have the <cligen/cligen.h> 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 <stdint.h> header file. */
#undef HAVE_STDINT_H
@ -87,9 +93,6 @@
/* Define to 1 if you have the <sys/types.h> header file. */
#undef HAVE_SYS_TYPES_H
/* Define to 1 if you have the <sys/ucred.h> header file. */
#undef HAVE_SYS_UCRED_H
/* Define to 1 if you have the <unistd.h> 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

View file

@ -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 --*/

View file

@ -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);

View file

@ -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);

View file

@ -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 */

View file

@ -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

View file

@ -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
*/

View file

@ -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);

View file

@ -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;

View file

@ -128,8 +128,8 @@ real ({digit}+[.]{digit}*)|({digit}*[.]{digit}+)
<TOKEN>following-sibling:: { clixon_xpath_parselval.intval = A_FOLLOWING_SIBLING; return AXISNAME; }
<TOKEN>namespace:: { clixon_xpath_parselval.intval = A_NAMESPACE; return AXISNAME; }
<TOKEN>parent:: { clixon_xpath_parselval.intval = A_PARENT; return AXISNAME; }
<TOKEN>preceding:: { clixon_xpath_parselval.intval = A_PRECEEDING; return AXISNAME; }
<TOKEN>preceding-sibling:: { clixon_xpath_parselval.intval = A_PRECEEDING_SIBLING; return AXISNAME; }
<TOKEN>preceding:: { clixon_xpath_parselval.intval = A_PRECEDING; return AXISNAME; }
<TOKEN>preceding-sibling:: { clixon_xpath_parselval.intval = A_PRECEDING_SIBLING; return AXISNAME; }
<TOKEN>self:: { clixon_xpath_parselval.intval = A_SELF; return AXISNAME; }
<TOKEN>current { clixon_xpath_parselval.string = strdup(yytext); return NODETYPE; }

View file

@ -27,7 +27,6 @@ cat <<EOF > $cfg
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
</clixon-config>
EOF

View file

@ -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 <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

239
test/test_nacm_credentials.sh Executable file
View file

@ -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 <<EOF > $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 <<EOF
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
<enable-nacm>false</enable-nacm>
<read-default>permit</read-default>
<write-default>deny</write-default>
<exec-default>deny</exec-default>
$NGROUPS
<rule-list>
<name>guest-acl</name>
<group>guest</group>
<rule>
<name>deny-ncm</name>
<module-name>*</module-name>
<access-operations>*</access-operations>
<action>deny</action>
<comment>
Do not allow guests any access to the NETCONF
monitoring information.
</comment>
</rule>
</rule-list>
<rule-list>
<name>limited-acl</name>
<group>limited</group>
<rule>
<name>permit-get</name>
<rpc-name>get</rpc-name>
<module-name>*</module-name>
<access-operations>exec</access-operations>
<action>permit</action>
<comment>
Allow get
</comment>
</rule>
<rule>
<name>permit-get-config</name>
<rpc-name>get-config</rpc-name>
<module-name>*</module-name>
<access-operations>exec</access-operations>
<action>permit</action>
<comment>
Allow get-config
</comment>
</rule>
</rule-list>
$NADMIN
</nacm>
<x xmlns="urn:example:nacm">0</x>
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 <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_SOCK_FAMILY>$family</CLICON_SOCK_FAMILY>
<CLICON_SOCK>$sock</CLICON_SOCK>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>$mode</CLICON_NACM_CREDENTIALS>
</clixon-config>
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 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><config><nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm"><enable-nacm>true</enable-nacm></nacm></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "commit it"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# raw socket test
if [ -n "$username" ]; then
XML="<rpc username=\"$username\"><get-config><source><running/></source><filter type=\"xpath\" select=\"/ex:x\" xmlns:ex=\"urn:example:nacm\"/></get-config></rpc>"
else
XML="<rpc><get-config><source><running/></source><filter type=\"xpath\" select=\"/ex:x\" xmlns:ex=\"urn:example:nacm\"/></get-config></rpc>"
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='^<rpc-reply><data><x xmlns="urn:example:nacm">0</x></data></rpc-reply>$'
ERROR='^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>'
# 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

View file

@ -32,6 +32,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
</clixon-config>
EOF

View file

@ -36,6 +36,7 @@ cat <<EOF > $cfg
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>external</CLICON_NACM_MODE>
<CLICON_NACM_FILE>$nacmfile</CLICON_NACM_FILE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

@ -46,6 +46,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

@ -48,6 +48,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

@ -52,6 +52,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

@ -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

81
test/test_sock.sh Executable file
View file

@ -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 <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_FEATURE>*:*</CLICON_FEATURE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>clixon-example</CLICON_YANG_MODULE_MAIN>
<CLICON_SOCK_FAMILY>$family</CLICON_SOCK_FAMILY>
<CLICON_SOCK_PORT>$port</CLICON_SOCK_PORT>
<CLICON_SOCK>$sock</CLICON_SOCK>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
</clixon-config>
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

View file

@ -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

184
util/clixon_util_socket.c Normal file
View file

@ -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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <syslog.h>
#include <assert.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/stat.h>
/* cligen */
#include <cligen/cligen.h>
/* 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 <level> \tDebug\n"
"\t-a <family>\tSocket address family (default UNIX)\n"
"\t-s <sockpath> \tPath to unix domain socket (or IP addr)\n"
"\t-f <file>\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 <sockpath>\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;
}

View file

@ -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 "<a><b/></a>" | 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 <level> \tDebug\n"

View file

@ -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;