Merge branch 'nacm' into develop

This commit is contained in:
Olof hagsand 2019-02-02 11:38:02 +01:00
commit cf4f626719
32 changed files with 1733 additions and 328 deletions

View file

@ -76,13 +76,19 @@
* Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list
* CLICON_YANG_MAIN_FILE Provides a filename with a single module filename.
* CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded.
* NACM extension (RFC8341)
* NACM module support (RFC8341 A1+A2)
* NACM (RFC8341) experimental
* Incoming RPC Message validation is supported (3.4.4)
* Data Node Access validation is supported (3.4.5), except:
* rule-type data-node path is not supported
* Outgoing noitification aithorization is _not_ supported (3.4.6)
* RPC:s are supported _except_:
* `copy-config`for other src/target combinations than running/startup (3.2.6)
* `commit` - NACM is applied to candidate and running operations only (3.2.8)
* Client-side RPC:s are _not_ supported.
* Recovery user "_nacm_recovery" added.
* Example use is restconf PUT when NACM edit-config is permitted, then automatic commit and discard are permitted using recovery user.
* Example user changed adm1 to andy to comply with RFC8341 example
### API changes on existing features (you may need to change your code)
* Added `username` argument on `xmldb_put()` datastore function for NACM data-node write checks
* Rearranged yang files
* Moved and updated all standard ietf and iana yang files from example and yang/ to `yang/standard`.
* Moved clixon yang files from yang to `yang/clixon`
@ -112,6 +118,7 @@
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
### Minor changes
* Added `xml_rootchild_node()` lib function as variant of `xml_rootchild()`
* Added -o "<option>=<value>" command-line option to all programs: backend, cli, netconf, restconf.
* Any config option from file can be overrided by giving them on command-line.
* Added -p <dir> command-line option to all programs: backend, cli, netconf, restconf.

View file

@ -220,35 +220,28 @@ so the clients can in principle fake a username.
NACM
====
Clixon includes an experimental Network Configuration Access Control Model (NACM) according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341). It has limited functionality.
Clixon includes an experimental Network Configuration Access Control Model (NACM) according to [RFC8341(NACM)](https://tools.ietf.org/html/rfc8341).
The support is as follows:
To enable NACM:
* There is a yang config variable `CLICON_NACM_MODE` to set whether NACM is disabled, uses internal(embedded) NACM configuration, or external configuration. (See yang/clixon-config.yang)
* If the mode is internal, NACM configurations is expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping.
* The `CLICON_NACM_MODE` config variable is by default `disabled`.
* If the mode is internal`, NACM configurations are expected to be in the regular configuration, managed by regular candidate/runing/commit procedures. This mode may have some problems with bootstrapping.
* If the mode is `external`, the `CLICON_NACM_FILE` yang config variable contains the name of a separate configuration file containing the NACM configurations. After changes in this file, the backend needs to be restarted.
* The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
* There are two [tests](test/README.md) using internal and external NACM config
* The backend provides a limited NACM support (when enabled) described below
NACM is implemented in the backend and a single access check is made
in `from_client_msg()` when an internal netconf RPC has
just been received and decoded. The code is in `nacm_access()`.
The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
The functionality is as follows:
* Notification is not supported
* Groups are supported
* Rule-lists are supported
* Rules are supported as follows
* module-name: fully supported
* access-operations: only '*' and 'exec' supported
* rpc-name: fully supported (eg edit-config/get-config, etc)
* action: fully supported (permit/deny)
NACM is implemented in the backend with incoming RPC and data node access control points.
The tests outlines an example of three groups (taken from the RFC): admin, limited and guest:
* admin: Full access
* limited: Read access (get and get-config)
* guest: No access
The functionality is as follows (references to sections in [RFC8341](https://tools.ietf.org/html/rfc8341)):
* Access control point support:
* Incoming RPC Message validation is supported (3.4.4)
* Data Node Access validation is supported (3.4.5), except:
* rule-type data-node path is not supported
* Outgoing noitification aithorization is _not_ supported (3.4.6)
* RPC:s are supported _except_:
* `copy-config`for other src/target combinations than running/startup (3.2.6)
* `commit` - NACM is applied to candidate and running operations only (3.2.8)
* Client-side RPC:s are _not_ supported.
Runtime
=======

View file

@ -177,14 +177,19 @@ netconf_db_find(cxobj *xn,
static int
from_client_get_config(clicon_handle h,
cxobj *xe,
char *username,
cbuf *cbret)
{
int retval = -1;
char *db;
cxobj *xfilter;
char *selector = "/";
cxobj *xret = NULL;
cbuf *cbx = NULL; /* Assist cbuf */
int retval = -1;
char *db;
cxobj *xfilter;
char *xpath = "/";
cxobj *xret = NULL;
cbuf *cbx = NULL; /* Assist cbuf */
cxobj *xnacm = NULL;
cxobj **xvec = NULL;
size_t xlen;
int ret;
if ((db = netconf_db_find(xe, "source")) == NULL){
clicon_err(OE_XML, 0, "db not found");
@ -201,13 +206,23 @@ from_client_get_config(clicon_handle h,
goto ok;
}
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((selector = xml_find_value(xfilter, "select"))==NULL)
selector="/";
if (xmldb_get(h, db, selector, 1, &xret) < 0){
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/";
if (xmldb_get(h, db, xpath, 1, &xret) < 0){
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
goto done;
goto ok;
}
/* Pre-NACM access step */
if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
goto done;
if (ret == 0){ /* Do NACM validation */
if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* NACM datanode/module read validation */
if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0)
goto done;
}
cprintf(cbret, "<rpc-reply>");
if (xret==NULL)
cprintf(cbret, "<data/>");
@ -221,6 +236,10 @@ from_client_get_config(clicon_handle h,
ok:
retval = 0;
done:
if (xnacm)
xml_free(xnacm);
if (xvec)
free(xvec);
if (cbx)
cbuf_free(cbx);
if (xret)
@ -356,6 +375,7 @@ client_statedata(clicon_handle h,
static int
from_client_get(clicon_handle h,
cxobj *xe,
char *username,
cbuf *cbret)
{
int retval = -1;
@ -363,7 +383,10 @@ from_client_get(clicon_handle h,
char *xpath = "/";
cxobj *xret = NULL;
int ret;
cxobj **xvec = NULL;
size_t xlen;
cxobj *xnacm = NULL;
if ((xfilter = xml_find(xe, "filter")) != NULL)
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
xpath="/";
@ -383,6 +406,16 @@ from_client_get(clicon_handle h,
goto done;
goto ok;
}
/* Pre-NACM access step */
if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
goto done;
if (ret == 0){ /* Do NACM validation */
if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
/* NACM datanode/module read validation */
if (nacm_datanode_read(xret, xvec, xlen, username, xnacm) < 0)
goto done;
}
cprintf(cbret, "<rpc-reply>"); /* OK */
if (xret==NULL)
cprintf(cbret, "<data/>");
@ -396,6 +429,10 @@ from_client_get(clicon_handle h,
ok:
retval = 0;
done:
if (xnacm)
xml_free(xnacm);
if (xvec)
free(xvec);
if (xret)
xml_free(xret);
return retval;
@ -412,6 +449,7 @@ static int
from_client_edit_config(clicon_handle h,
cxobj *xn,
int mypid,
char *username,
cbuf *cbret)
{
int retval = -1;
@ -485,7 +523,7 @@ from_client_edit_config(clicon_handle h,
*/
if (xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0)
goto done;
if ((ret = xmldb_put(h, target, operation, xc, cbret)) < 0){
if ((ret = xmldb_put(h, target, operation, xc, username, cbret)) < 0){
clicon_debug(1, "%s ERROR PUT", __FUNCTION__);
if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0)
goto done;
@ -690,6 +728,10 @@ from_client_kill_session(clicon_handle h,
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK
* @retval -1 Error. Send error message back to client.
* NACM: If source running and target startup --> only exec permission
* else:
* - omit data nodes to which the client does not have read access
* - access denied if user lacks create/delete/update
*/
static int
from_client_copy_config(clicon_handle h,
@ -963,7 +1005,8 @@ from_client_msg(clicon_handle h,
yang_spec *yspec;
yang_stmt *ye;
yang_stmt *ymod;
cxobj *xnacm = NULL;
clicon_debug(1, "%s", __FUNCTION__);
pid = ce->ce_pid;
yspec = clicon_dbspec_yang(h);
@ -996,6 +1039,8 @@ from_client_msg(clicon_handle h,
goto reply;
xe = NULL;
username = xml_find_value(x, "username");
/* May be used by callbacks, etc */
clicon_username_set(h, username);
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
rpc = xml_name(xe);
if ((ye = xml_spec(xe)) == NULL){
@ -1009,17 +1054,25 @@ from_client_msg(clicon_handle h,
}
module = ymod->ys_argument;
clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc);
/* Make NACM access control if enabled as "internal"*/
if ((ret = nacm_access(h, rpc, module, username, cbret)) < 0)
/* Pre-NACM access step */
xnacm = NULL;
if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
goto done;
if (ret == 0)
goto reply;
if (ret == 0){ /* Do NACM validation */
/* NACM rpc operation exec validation */
if ((ret = nacm_rpc(rpc, module, username, xnacm, cbret)) < 0)
goto done;
if (xnacm)
xml_free(xnacm);
if (ret == 0) /* Not permitted and cbret set */
goto reply;
}
if (strcmp(rpc, "get-config") == 0){
if (from_client_get_config(h, xe, cbret) <0)
if (from_client_get_config(h, xe, username, cbret) <0)
goto done;
}
else if (strcmp(rpc, "edit-config") == 0){
if (from_client_edit_config(h, xe, pid, cbret) <0)
if (from_client_edit_config(h, xe, pid, username, cbret) <0)
goto done;
}
else if (strcmp(rpc, "copy-config") == 0){
@ -1039,7 +1092,7 @@ from_client_msg(clicon_handle h,
goto done;
}
else if (strcmp(rpc, "get") == 0){
if (from_client_get(h, xe, cbret) < 0)
if (from_client_get(h, xe, username, cbret) < 0)
goto done;
}
else if (strcmp(rpc, "close-session") == 0){

View file

@ -285,7 +285,8 @@ candidate_commit(clicon_handle h,
/* Optionally write (potentially modified) tree back to candidate */
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target, cbret)) < 0)
if ((ret = xmldb_put(h, candidate, OP_REPLACE, td->td_target,
clicon_username_get(h), cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
@ -322,6 +323,10 @@ candidate_commit(clicon_handle h,
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client
* @retval -1 (Local) Error
* NACM: The server MUST determine the exact nodes in the running
* configuration datastore that are actually different and only check
* "create", "update", and "delete" access permissions for this set of
* nodes, which could be empty.
*/
int
from_client_commit(clicon_handle h,
@ -367,6 +372,7 @@ from_client_commit(clicon_handle h,
* @param[out] cbret Return xml value cligen buffer
* @retval 0 OK. This may indicate both ok and err msg back to client
* @retval -1 (Local) Error
* NACM: No datastore permissions are needed.
*/
int
from_client_discard_changes(clicon_handle h,
@ -440,7 +446,8 @@ from_client_validate(clicon_handle h,
}
/* Optionally write (potentially modified) tree back to candidate */
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target, cbret)) < 0)
if ((ret = xmldb_put(h, "candidate", OP_REPLACE, td->td_target,
clicon_username_get(h), cbret)) < 0)
goto done;
goto ok;
}

View file

@ -195,7 +195,7 @@ db_merge(clicon_handle h,
if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0)
goto done;
/* Merge xml into db2. Without commit */
retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, cbret);
retval = xmldb_put(h, (char*)db2, OP_MERGE, xt, clicon_username_get(h), cbret);
done:
if (xt)
xml_free(xt);
@ -318,7 +318,7 @@ load_extraxml(clicon_handle h,
if (xml_rootchild(xt, 0, &xt) < 0)
goto done;
/* Merge user reset state */
retval = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret);
retval = xmldb_put(h, (char*)db, OP_MERGE, xt, clicon_username_get(h), cbret);
done:
if (fd != -1)
close(fd);
@ -857,6 +857,10 @@ main(int argc,
goto done;
if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0)
goto done;
if (xmldb_setopt(h, "nacm_mode", (void*)nacm_mode) < 0)
goto done;
if (xmldb_setopt(h, "nacm_xtree", (void*)clicon_nacm_ext(h)) < 0)
goto done;
/* Startup mode needs to be defined, */
startup_mode = clicon_startup_mode(h);
if (startup_mode == -1){
@ -906,7 +910,6 @@ main(int argc,
}
}
/* Write pid-file */
if ((pid = pidfile_write(pidfile)) < 0)
goto done;

View file

@ -212,6 +212,7 @@ yang2cli_var_sub(clicon_handle h,
if (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0){
cprintf(cb, " choice:");
i = 0;
yi = NULL;
while ((yi = yn_each((yang_node*)ytype, yi)) != NULL){
if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT)
continue;

View file

@ -779,7 +779,7 @@ api_data_put(clicon_handle h,
goto done;
goto ok;
}
}
}
/* The message-body MUST contain exactly one instance of the
* expected data resource.
*/
@ -808,6 +808,7 @@ api_data_put(clicon_handle h,
goto ok;
}
}
/* Add operation (create/replace) as attribute */
if ((xa = xml_new("operation", x, NULL)) == NULL)
goto done;
@ -832,6 +833,7 @@ api_data_put(clicon_handle h,
xml_name_set(xtop, "config");
}
else {
clicon_debug(1, "%s x:%s xbot:%s",__FUNCTION__, xml_name(x), xml_name(xbot));
/* Check same symbol in api-path as data */
if (strcmp(xml_name(x), xml_name(xbot))){
if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0)
@ -897,7 +899,6 @@ api_data_put(clicon_handle h,
by NACM */
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
cprintf(cbx, "<commit/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){

View file

@ -248,7 +248,7 @@ main(int argc, char **argv)
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;
}
if (xmldb_put(h, db, op, xt, cbret) < 1)
if (xmldb_put(h, db, op, xt, NULL, cbret) < 1)
goto done;
}

View file

@ -88,6 +88,8 @@ struct text_handle {
Assumes single backend*/
char *th_format; /* Datastroe format: xml / json */
int th_pretty; /* Store xml/json pretty-printed. */
char *th_nacm_mode;
cxobj *th_nacm_xtree;
};
/* Struct per database in hash */
@ -207,6 +209,8 @@ text_disconnect(xmldb_handle xh)
}
hash_free(th->th_dbs);
}
if (th->th_nacm_mode)
free(th->th_nacm_mode);
free(th);
}
retval = 0;
@ -239,6 +243,10 @@ text_getopt(xmldb_handle xh,
*value = th->th_format;
else if (strcmp(optname, "pretty") == 0)
*value = &th->th_pretty;
else if (strcmp(optname, "nacm_mode") == 0)
*value = &th->th_nacm_mode;
else if (strcmp(optname, "nacm_xtree") == 0)
*value = th->th_nacm_xtree;
else{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
@ -288,6 +296,15 @@ text_setopt(xmldb_handle xh,
else if (strcmp(optname, "pretty") == 0){
th->th_pretty = (intptr_t)value;
}
else if (strcmp(optname, "nacm_mode") == 0){
if (value && (th->th_nacm_mode = strdup((char*)value)) == NULL){
clicon_err(OE_UNIX, 0, "strdup");
goto done;
}
}
else if (strcmp(optname, "nacm_xtree") == 0){
th->th_nacm_xtree = (cxobj*)value;
}
else{
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
goto done;
@ -421,10 +438,17 @@ xml_copy_marked(cxobj *x0,
* The function returns a minimal tree that includes all sub-trees that match
* xpath.
* This is a clixon datastore plugin of the the xmldb api
* @see xmldb_get
* @param[in] h Clicon handle
* @param[in] dbname Name of database to search in (filename including dir path
* @param[in] xpath String with XPATH syntax. or NULL for all
* @param[in] config If set only configuration data, else also state
* @param[out] xret Single return XML tree. Free with xml_free()
* @retval 0 OK
* @retval -1 Error
* @see xmldb_get the generic API function
*/
int
text_get(xmldb_handle xh,
text_get(xmldb_handle xh,
const char *db,
char *xpath,
int config,
@ -434,6 +458,7 @@ text_get(xmldb_handle xh,
char *dbfile = NULL;
yang_spec *yspec;
cxobj *xt = NULL;
cxobj *x;
int fd = -1;
cxobj **xvec = NULL;
size_t xlen;
@ -486,7 +511,6 @@ text_get(xmldb_handle xh,
*/
} /* xt == NULL */
/* Here xt looks like: <config>...</config> */
if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
@ -496,11 +520,13 @@ text_get(xmldb_handle xh,
*/
if (xvec != NULL)
for (i=0; i<xlen; i++){
xml_flag_set(xvec[i], XML_FLAG_MARK);
if (th->th_cache)
xml_apply_ancestor(xvec[i], (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
x = xvec[i];
xml_flag_set(x, XML_FLAG_MARK);
if (1 || th->th_cache)
xml_apply_ancestor(x, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
}
if (th->th_cache){
/* Copy the matching parts of the (relevant) XML tree.
* If cache was NULL, also write to datastore cache
@ -534,13 +560,16 @@ text_get(xmldb_handle xh,
/* reset flag */
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
goto done;
/* filter out state (operations) data if config not set. Mark all nodes
that are not config data */
if (config && xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
goto done;
/* Remove (prune) nodes that are marked (that does not pass test) */
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
goto done;
if (config){
if (xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
goto done;
/* Remove (prune) nodes that are marked (that does not pass test) */
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
goto done;
}
/* Add default values (if not set) */
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
goto done;
@ -572,6 +601,9 @@ text_get(xmldb_handle xh,
* @param[in] x0p Parent of x0
* @param[in] x1 xml tree which modifies base
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] username User name of requestor for nacm
* @param[in] xnacm NACM XML tree
* @param[in] permit If set, NACM has permitted this tree on an upper level
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
@ -586,6 +618,9 @@ text_modify(struct text_handle *th,
cxobj *x0p,
cxobj *x1,
enum operation_type op,
char *username,
cxobj *xnacm,
int permit,
cbuf *cbret)
{
int retval = -1;
@ -597,6 +632,7 @@ text_modify(struct text_handle *th,
cxobj *x0c; /* base child */
cxobj *x0b; /* base body */
cxobj *x1c; /* mod child */
char *x0bstr; /* mod body string */
char *x1bstr; /* mod body string */
yang_stmt *yc; /* yang child */
cxobj **x0vec = NULL;
@ -623,6 +659,13 @@ text_modify(struct text_handle *th,
case OP_MERGE:
case OP_REPLACE:
if (x0==NULL){
if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
// int iamkey=0;
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
@ -649,13 +692,21 @@ text_modify(struct text_handle *th,
}
}
if (x1bstr){
if ((x0b = xml_body_get(x0)) == NULL){
if ((x0b = xml_new("body", x0, NULL)) == NULL)
goto done;
xml_type_set(x0b, CX_BODY);
if ((x0b = xml_body_get(x0)) != NULL){
x0bstr = xml_value(x0b);
if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){
if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x1,
x0bstr==NULL?NACM_CREATE:NACM_UPDATE,
username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (xml_value_set(x0b, x1bstr) < 0)
goto done;
}
}
if (xml_value_set(x0b, x1bstr) < 0)
goto done;
}
break;
case OP_DELETE:
@ -666,7 +717,14 @@ text_modify(struct text_handle *th,
}
case OP_REMOVE: /* fall thru */
if (x0){
xml_purge(x0);
if ((op != OP_NONE) && !permit && xnacm){
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (xml_purge(x0) < 0)
goto done;
}
break;
default:
@ -682,6 +740,13 @@ text_modify(struct text_handle *th,
goto fail;
}
case OP_REPLACE: /* fall thru */
if (xnacm && !permit){
if ((ret = nacm_datanode_write(NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
if (x0){
xml_purge(x0);
x0 = NULL;
@ -689,10 +754,21 @@ text_modify(struct text_handle *th,
case OP_MERGE: /* fall thru */
case OP_NONE:
/* Special case: anyxml, just replace tree,
See 7.10.3 of RFC6020bis */
See rfc6020 7.10.3:n
An anyxml node is treated as an opaque chunk of data. This data
can be modified in its entirety only.
Any "operation" attributes present on subelements of an anyxml
node are ignored by the NETCONF server.*/
if (y0->yn_keyword == Y_ANYXML){
if (op == OP_NONE)
break;
if (xnacm && op==OP_MERGE && !permit){
if ((ret = nacm_datanode_write(NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
if (x0){
xml_purge(x0);
}
@ -703,6 +779,13 @@ text_modify(struct text_handle *th,
break;
}
if (x0==NULL){
if (xnacm && op==OP_MERGE && !permit){
if ((ret = nacm_datanode_write(NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
goto done;
/* Copy xmlns attributes */
@ -755,7 +838,8 @@ text_modify(struct text_handle *th,
x1cname = xml_name(x1c);
x0c = x0vec[i++];
yc = yang_find_datanode(y0, x1cname);
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op,
username, xnacm, permit, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (ret == 0)
@ -769,8 +853,16 @@ text_modify(struct text_handle *th,
goto fail;
}
case OP_REMOVE: /* fall thru */
if (x0)
xml_purge(x0);
if (x0){
if (xnacm){
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
}
if (xml_purge(x0) < 0)
goto done;
}
break;
default:
break;
@ -793,6 +885,8 @@ text_modify(struct text_handle *th,
* @param[in] x1 xml tree which modifies base
* @param[in] yspec Top-level yang spec (if y is NULL)
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
* @param[in] username User name of requestor for nacm
* @param[in] xnacm NACM XML tree
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
* @retval -1 Error
* @retval 0 Failed (cbret set)
@ -805,6 +899,8 @@ text_modify_top(struct text_handle *th,
cxobj *x1,
yang_spec *yspec,
enum operation_type op,
char *username,
cxobj *xnacm,
cbuf *cbret)
{
int retval = -1;
@ -815,6 +911,7 @@ text_modify_top(struct text_handle *th,
yang_stmt *ymod;/* yang module */
char *opstr;
int ret;
int permit = 0;
/* Assure top-levels are 'config' */
assert(x0 && strcmp(xml_name(x0),"config")==0);
@ -825,35 +922,56 @@ text_modify_top(struct text_handle *th,
if (xml_operation(opstr, &op) < 0)
goto done;
/* Special case if x1 is empty, top-level only <config/> */
if (xml_child_nr(x1) == 0){
if (xml_child_nr(x0)) /* base tree not empty */
if (xml_child_nr_type(x1, CX_ELMNT) == 0){
if (xml_child_nr_type(x0, CX_ELMNT)){ /* base tree not empty */
switch(op){
case OP_DELETE:
case OP_REMOVE:
case OP_REPLACE:
x0c = NULL;
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL)
xml_purge(x0c);
if (xnacm){
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
while ((x0c = xml_child_i(x0, 0)) != 0)
if (xml_purge(x0c) < 0)
goto done;
break;
default:
break;
}
}
else /* base tree empty */
switch(op){
#if 0 /* According to RFC6020 7.5.8 you cant delete a non-existing object.
On the other hand, the top-level cannot be removed anyway.
Additionally, I think this is irritating so I disable it.
I.e., curl -u andy:bar -sS -X DELETE http://localhost/restconf/data
*/
case OP_DELETE:
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
goto done;
goto fail;
break;
#endif
default:
break;
}
}
/* Special case top-level replace */
if (op == OP_REPLACE || op == OP_DELETE){
x0c = NULL;
else if (op == OP_REPLACE || op == OP_DELETE){
if (xnacm && !permit){
if ((ret = nacm_datanode_write(NULL, x1, NACM_UPDATE, username, xnacm, cbret)) < 0)
goto done;
if (ret == 0)
goto fail;
permit = 1;
}
while ((x0c = xml_child_i(x0, 0)) != 0)
xml_purge(x0c);
if (xml_purge(x0c) < 0)
goto done;
}
/* Loop through children of the modification tree */
x1c = NULL;
@ -885,7 +1003,8 @@ text_modify_top(struct text_handle *th,
x0c = NULL;
}
#endif
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op, cbret)) < 0)
if ((ret = text_modify(th, x0c, (yang_node*)yc, x0, x1c, op,
username, xnacm, permit, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (ret == 0)
@ -939,13 +1058,23 @@ xml_container_presence(cxobj *x,
/*! Modify database provided an xml tree and an operation
* This is a clixon datastore plugin of the the xmldb api
* @see xmldb_put
* @param[in] h CLICON handle
* @param[in] db running or candidate
* @param[in] op Top-level operation, can be superceded by other op in tree
* @param[in] x1 xml-tree. Top-level symbol is dummy
* @param[in] username User name for nacm
* @param[out] cbret Initialized cligen buffer. On exit contains XML if retval == 0
* @retval 1 OK
* @retval 0 Failed, cbret contains error xml message
* @retval -1 Error
* @see xmldb_put the generic API function
*/
int
text_put(xmldb_handle xh,
const char *db,
enum operation_type op,
cxobj *x1,
char *username,
cbuf *cbret)
{
int retval = -1;
@ -958,6 +1087,7 @@ text_put(xmldb_handle xh,
cxobj *x0 = NULL;
struct db_element *de = NULL;
int ret;
cxobj *xnacm = NULL;
if (cbret == NULL){
clicon_err(OE_XML, EINVAL, "cbret is NULL");
@ -1017,12 +1147,34 @@ text_put(xmldb_handle xh,
#if 0 /* debug */
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
#endif
#if 1
{
char *mode;
cxobj *xnacm0 = NULL;
mode = th->th_nacm_mode;
if (mode){
if (strcmp(mode, "external")==0)
xnacm0 = th->th_nacm_xtree;
else if (strcmp(mode, "internal")==0)
xnacm0 = x0;
}
if (xnacm0 != NULL &&
(xnacm = xpath_first(xnacm0, "nacm")) != NULL){
/* Pre-NACM access step */
if ((ret = nacm_access(mode, xnacm, username)) < 0)
goto done;
}
/* Here assume if xnacm is set (actually may be ret==0?) do NACM */
}
#endif
/*
* Modify base tree x with modification x1. This is where the
* new tree is made.
*/
if ((ret = text_modify_top(th, x0, x1, yspec, op, cbret)) < 0)
if ((ret = text_modify_top(th, x0, x1, yspec, op, username, xnacm, cbret)) < 0)
goto done;
/* If xml return - ie netconf error xml tree, then stop and return OK */
if (ret == 0)

View file

@ -40,7 +40,7 @@
* Prototypes
*/
int text_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop);
int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret);
int text_put(xmldb_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret);
int text_dump(FILE *f, char *dbfilename, char *rxkey);
int text_copy(xmldb_handle h, const char *from, const char *to);
int text_lock(xmldb_handle h, const char *db, int pid);

View file

@ -306,6 +306,7 @@ You may also add a default method in the configuration file:
Yes. Systemd example files are provide for the backend and the
restconf daemon as part of the [example](../example/systemd).
## How can I add extra XML?
There are two ways to add extra XML to running database after start. Note that this XML is not "committed" into running.
@ -474,6 +475,14 @@ To authenticate, the callback needs to return the value 1 and supply a username.
See [../apps/example/example_restconf.c] example_restconf_credentials() for
an example of HTTP basic auth.
## What about access control?
Clixon has experimental support of the Network Configuration Access
Control Model defined in [RFC8341](https://tools.ietf.org/html/rfc8341)
Incoming RPC and data node access points are supported with some
limitations. See the (README)(../README.md) for more information.
## How do I write a CLI translator function?
The CLI can perform variable translation. This is useful if you want to

View file

@ -243,7 +243,7 @@ example_reset(clicon_handle h,
goto done;
}
/* Merge user reset state */
if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, cbret)) < 0)
if ((ret = xmldb_put(h, (char*)db, OP_MERGE, xt, clicon_username_get(h), cbret)) < 0)
goto done;
if (ret == 0){
clicon_err(OE_XML, 0, "Error when writing to XML database: %s",

View file

@ -75,7 +75,7 @@ nacm_statedata(clicon_handle h,
cxobj **xvec = NULL;
/* Example of (static) statedata, real code would poll state */
if (xml_parse_string("<nacm>"
if (xml_parse_string("<nacm xmlns=\"urn:ietf:params:xml:ns:yang:ietf-netconf-acm\">"
"<denied-data-writes>0</denied-data-writes>"
"<denied-operations>0</denied-operations>"
"<denied-notifications>0</denied-notifications>"

View file

@ -45,10 +45,29 @@
*/
#define NACM_RECOVERY_USER "_nacm_recovery"
/*
* Types
*/
/* NACM access rights,
* Note that these are not the same as netconf operations
* @see rfc8341 3.2.2
* @see enum operation_type Netconf operations
*/
enum nacm_access{
NACM_CREATE,
NACM_READ,
NACM_UPDATE,
NACM_DELETE,
NACM_EXEC
};
/*
* Prototypes
*/
int nacm_access(clicon_handle h, char *rpc, char *module,
char *username, cbuf *cbret);
int nacm_rpc(char *rpc, char *module, char *username, cxobj *xnacm, cbuf *cbret);
int nacm_datanode_read(cxobj *xt, cxobj **xvec, size_t xlen, char *username, cxobj *nacm_xtree);
int nacm_datanode_write(cxobj *xt, cxobj *xr, enum nacm_access access,
char *username, cxobj *xnacm, cbuf *cbret);
int nacm_access_pre(clicon_handle h, char *username, cxobj **xnacmp);
int nacm_access(char *mode, cxobj *xnacmin, char *username);
#endif /* _CLIXON_NACM_H */

View file

@ -138,6 +138,7 @@ int xml_purge(cxobj *xc);
int xml_child_rm(cxobj *xp, int i);
int xml_rm(cxobj *xc);
int xml_rootchild(cxobj *xp, int i, cxobj **xcp);
int xml_rootchild_node(cxobj *xp, cxobj *xc);
char *xml_body(cxobj *xn);
cxobj *xml_body_get(cxobj *xn);

View file

@ -78,7 +78,7 @@ typedef int (xmldb_setopt_t)(xmldb_handle xh, char *optname, void *value);
typedef int (xmldb_get_t)(xmldb_handle xh, const char *db, char *xpath, int config, cxobj **xtop);
/* Type of xmldb put function */
typedef int (xmldb_put_t)(xmldb_handle xh, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret);
typedef int (xmldb_put_t)(xmldb_handle xh, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret);
/* Type of xmldb copy function */
typedef int (xmldb_copy_t)(xmldb_handle xh, const char *from, const char *to);
@ -139,7 +139,7 @@ int xmldb_disconnect(clicon_handle h);
int xmldb_getopt(clicon_handle h, char *optname, void **value);
int xmldb_setopt(clicon_handle h, char *optname, void *value);
int xmldb_get(clicon_handle h, const char *db, char *xpath, int config, cxobj **xtop);
int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt, cbuf *cbret);
int xmldb_put(clicon_handle h, const char *db, enum operation_type op, cxobj *xt, char *username, cbuf *cbret);
int xmldb_copy(clicon_handle h, const char *from, const char *to);
int xmldb_lock(clicon_handle h, const char *db, int pid);
int xmldb_unlock(clicon_handle h, const char *db);

View file

@ -72,13 +72,16 @@
* Incoming RPC Message Validation Step 7 (c)
* The rule's "access-operations" leaf has the "exec" bit set or
* has the special value "*".
* @param[in] mode Primary mode, eg read, create, update, delete, exec
* @param[in] mode2 Secondary mode, eg "write"
* @retval 0 No match
* @retval 1 Match
* @note access_operations is bit-fields
*/
static int
nacm_match_access(char *access_operations,
char *mode)
match_access(char *access_operations,
char *mode,
char *mode2)
{
if (access_operations==NULL)
return 0;
@ -86,33 +89,21 @@ nacm_match_access(char *access_operations,
return 1;
if (strstr(access_operations, mode)!=NULL)
return 1;
if (mode2 && strstr(access_operations, mode2)!=NULL)
return 1;
return 0;
}
/*! Match nacm single rule. Either match with access or deny. Or not match.
* @param[in] h Clicon handle
* @param[in] name rpc name
* @param[in] rpc rpc name
* @param[in] module Yang module name
* @param[in] xrule NACM rule XML tree
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
* @retval -1 Error
* @retval 0 Matching rule AND Not access and cbret set
* @retval 1 Matchung rule AND Access
* @retval 1 Matching rule AND Access
* @retval 2 No matching rule Goto step 10
* From RFC8341 3.4.4. Incoming RPC Message Validation
+---------+-----------------+---------------------+-----------------+
| Method | Resource class | NETCONF operation | Access |
| | | | operation |
+---------+-----------------+---------------------+-----------------+
| OPTIONS | all | none | none |
| HEAD | all | <get>, <get-config> | read |
| GET | all | <get>, <get-config> | read |
| POST | datastore, data | <edit-config> | create |
| POST | operation | specified operation | execute |
| PUT | data | <edit-config> | create, update |
| PUT | datastore | <copy-config> | update |
| PATCH | data, datastore | <edit-config> | update |
| DELETE | data | <edit-config> | delete |
* @see RFC8341 3.4.4. Incoming RPC Message Validation
7.(cont) A rule matches if all of the following criteria are met:
* The rule's "module-name" leaf is "*" or equals the name of
the YANG module where the protocol operation is defined.
@ -126,105 +117,83 @@ nacm_match_access(char *access_operations,
has the special value "*".
*/
static int
nacm_match_rule(clicon_handle h,
char *name,
char *module,
cxobj *xrule,
cbuf *cbret)
nacm_rule_rpc(char *rpc,
char *module,
cxobj *xrule)
{
int retval = -1;
char *module_rule; /* rule module name */
char *rpc_rule;
char *access_operations;
char *action;
module_rule = xml_find_body(xrule, "module-name");
rpc_rule = xml_find_body(xrule, "rpc-name");
/* XXX access_operations can be a set of bits */
access_operations = xml_find_body(xrule, "access-operations");
action = xml_find_body(xrule, "action");
clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__,
module_rule, rpc_rule, access_operations, action);
if (module_rule &&
(strcmp(module_rule,"*")==0 || strcmp(module_rule,module)==0)){
if (nacm_match_access(access_operations, "exec")){
if (rpc_rule==NULL ||
strcmp(rpc_rule, "*")==0 || strcmp(rpc_rule, name)==0){
/* Here is a matching rule */
if (action && strcmp(action, "permit")==0){
retval = 1;
goto done;
}
else{
if (netconf_access_denied(cbret, "protocol", "access denied") < 0)
goto done;
retval = 0;
goto done;
}
}
}
/* 7a) The rule's "module-name" leaf is "*" or equals the name of
the YANG module where the protocol operation is defined. */
if ((module_rule = xml_find_body(xrule, "module-name")) == NULL)
goto nomatch;
if (strcmp(module_rule,"*") && strcmp(module_rule,module))
goto nomatch;
/* 7b) Either (1) the rule does not have a "rule-type" defined or
(2) the "rule-type" is "protocol-operation" and the
"rpc-name" is "*" or equals the name of the requested
protocol operation. */
if ((rpc_rule = xml_find_body(xrule, "rpc-name")) == NULL){
if (xml_find_body(xrule, "path") || xml_find_body(xrule, "notification-name"))
goto nomatch;
}
retval = 2; /* no matching rule */
if (rpc_rule && (strcmp(rpc_rule, "*") && strcmp(rpc_rule, rpc)))
goto nomatch;
/* 7c) The rule's "access-operations" leaf has the "exec" bit set or
has the special value "*". */
access_operations = xml_find_body(xrule, "access-operations");
if (!match_access(access_operations, "exec", NULL))
goto nomatch;
retval = 1;
done:
return retval;
nomatch:
retval = 0;
goto done;
}
/*! Process a nacm message control
* @param[in] h Clicon handle
* @param[in] mode NACMmode, internal or external
* @param[in] name rpc name
* @param[in] username
* @param[in] xtop
/*! Process nacm incoming RPC message validation steps
* @param[in] module Yang module name
* @param[in] rpc rpc name
* @param[in] username User name of requestor
* @param[in] xnacm NACM xml tree
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
* @retval -1 Error
* @retval 0 Not access and cbret set
* @retval 1 Access
* @see RFC8341 3.4.4. Incoming RPC Message Validation
* @see nacm_datanode_write
* @see nacm_datanode_read
*/
static int
nacm_rpc_validation(clicon_handle h,
char *name,
char *module,
char *username,
cxobj *xtop,
cbuf *cbret)
int
nacm_rpc(char *rpc,
char *module,
char *username,
cxobj *xnacm,
cbuf *cbret)
{
int retval = -1;
cxobj *xacm;
cxobj *x;
cxobj *xrule;
char *enabled = NULL;
cxobj **gvec = NULL; /* groups */
size_t glen;
cxobj *xrlist;
cxobj *rlist;
cxobj **rlistvec = NULL; /* rule-list */
size_t rlistlen;
cxobj **rvec = NULL; /* rules */
size_t rlen;
int ret;
int i, j;
char *exec_default = NULL;
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
operation is permitted. (or config does not exist) */
if ((xacm = xpath_first(xtop, "nacm")) == NULL)
goto permit;
exec_default = xml_find_body(xacm, "exec-default");
if ((x = xpath_first(xacm, "enable-nacm")) == NULL)
goto permit;
enabled = xml_body(x);
if (strcmp(enabled, "true") != 0)
goto permit;
/* 2. If the requesting session is identified as a recovery session,
then the protocol operation is permitted. NYI */
if (strcmp(username, NACM_RECOVERY_USER) == 0)
goto permit;
char *gname;
char *action;
int match= 0;
/* 3. If the requested operation is the NETCONF <close-session>
protocol operation, then the protocol operation is permitted.
*/
if (strcmp(name, "close-session") == 0)
if (strcmp(rpc, "close-session") == 0)
goto permit;
/* 4. Check all the "group" entries to see if any of them contain a
"user-name" entry that equals the username for the session
@ -234,7 +203,7 @@ nacm_rpc_validation(clicon_handle h,
if (username == NULL)
goto step10;
/* User's group */
if (xpath_vec(xacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0)
if (xpath_vec(xnacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0)
goto done;
/* 5. If no groups are found, continue with step 10. */
if (glen == 0)
@ -243,15 +212,14 @@ nacm_rpc_validation(clicon_handle h,
configuration. If a rule-list's "group" leaf-list does not
match any of the user's groups, proceed to the next rule-list
entry. */
if (xpath_vec(xacm, "rule-list", &rlistvec, &rlistlen) < 0)
if (xpath_vec(xnacm, "rule-list", &rlistvec, &rlistlen) < 0)
goto done;
for (i=0; i<rlistlen; i++){
xrlist = rlistvec[i];
rlist = rlistvec[i];
/* Loop through user's group to find match in this rule-list */
for (j=0; j<glen; j++){
char *gname;
gname = xml_find_body(gvec[j], "name");
if (xpath_first(xrlist, ".[group='%s']", gname)!=NULL)
if (xpath_first(rlist, ".[group='%s']", gname)!=NULL)
break; /* found */
}
if (j==glen) /* not found */
@ -260,24 +228,33 @@ nacm_rpc_validation(clicon_handle h,
until a rule that matches the requested access operation is
found.
*/
if (xpath_vec(xrlist, "rule", &rvec, &rlen) < 0)
if (xpath_vec(rlist, "rule", &rvec, &rlen) < 0)
goto done;
for (j=0; j<rlen; j++){
xrule = rvec[j];
/* -1 error, 0 deny, 1 permit, 2 continue */
if ((ret = nacm_match_rule(h, name, module, xrule, cbret)) < 0)
if ((match = nacm_rule_rpc(rpc, module, xrule)) < 0)
goto done;
switch(ret){
case 0: /* deny */
goto deny;
if (match)
break;
case 1: /* permit */
goto permit;
break;
case 2: /* no match, continue */
break;
}
}
if (match)
break;
if (rvec){
free(rvec);
rvec=NULL;
}
}
if (match){
if ((action = xml_find_body(xrule, "action")) == NULL)
goto step10;
if (strcmp(action, "deny")==0){
if (netconf_access_denied(cbret, "application", "access denied") < 0)
goto done;
goto deny;
}
else if (strcmp(action, "permit")==0)
goto permit;
}
step10:
/* 10. If the requested protocol operation is defined in a YANG module
@ -287,16 +264,17 @@ nacm_rpc_validation(clicon_handle h,
/* 11. If the requested protocol operation is the NETCONF
<kill-session> or <delete-config>, then the protocol operation
is denied. */
if (strcmp(name, "kill-session")==0 || strcmp(name, "delete-config")==0){
if (netconf_access_denied(cbret, "protocol", "default deny") < 0)
if (strcmp(rpc, "kill-session")==0 || strcmp(rpc, "delete-config")==0){
if (netconf_access_denied(cbret, "application", "default deny") < 0)
goto done;
goto deny;
}
/* 12. If the "exec-default" leaf is set to "permit", then permit the
protocol operation; otherwise, deny the request. */
exec_default = xml_find_body(xnacm, "exec-default");
if (exec_default ==NULL || strcmp(exec_default, "permit")==0)
goto permit;
if (netconf_access_denied(cbret, "protocol", "default deny") < 0)
if (netconf_access_denied(cbret, "application", "default deny") < 0)
goto done;
goto deny;
permit:
@ -316,56 +294,617 @@ nacm_rpc_validation(clicon_handle h,
goto done;
}
/*! Make nacm access control
* @param[in] h Clicon handle
* @param[in] name rpc name
* @param[in] username
/*---------------------------------------------------------------
* Datanode/module read and write
*/
/*! We have a rule matching user group. Now match proper write operation and module
* @retval -1 Error
* @retval 0 No Match
* @retval 1 Match
* @see RFC8341 3.4.5. Data Node Access Validation point (6)
*/
static int
nacm_rule_datanode(cxobj *xt,
cxobj *xr,
cxobj *xrule,
enum nacm_access access)
{
int retval = -1;
char *path;
char *access_operations;
char *module_rule; /* rule module name */
yang_stmt *ys;
yang_stmt *ymod;
char *module;
cxobj *xpath; /* xpath match */
cxobj *xp; /* parent */
/* 6a) The rule's "module-name" leaf is "*" or equals the name of
* the YANG module where the requested data node is defined. */
if ((module_rule = xml_find_body(xrule, "module-name")) == NULL)
goto nomatch;
if (strcmp(module_rule,"*")!=0){
if ((ys = xml_spec(xr)) == NULL)
goto nomatch;
ymod = ys_module(ys);
module = ymod->ys_argument;
if (strcmp(module, module_rule) != 0)
goto nomatch;
}
/* 6b) Either (1) the rule does not have a "rule-type" defined or
(2) the "rule-type" is "data-node" and the "path" matches the
requested data node, action node, or notification node. A
path is considered to match if the requested node is the node
specified by the path or is a descendant node of the path.*/
if ((path = xml_find_body(xrule, "path")) == NULL){
if (xml_find_body(xrule, "rpc-name") ||xml_find_body(xrule, "notification-name"))
goto nomatch;
}
access_operations = xml_find_body(xrule, "access-operations");
switch (access){
case NACM_READ:
/* 6c) For a "read" access operation, the rule's "access-operations"
leaf has the "read" bit set or has the special value "*" */
if (!match_access(access_operations, "read", NULL))
goto nomatch;
break;
case NACM_CREATE:
/* 6d) For a "create" access operation, the rule's "access-operations"
leaf has the "create" bit set or has the special value "*". */
if (!match_access(access_operations, "create", "write"))
goto nomatch;
break;
case NACM_DELETE:
/* 6e) For a "delete" access operation, the rule's "access-operations"
leaf has the "delete" bit set or has the special value "*". */
if (!match_access(access_operations, "delete", "write"))
goto nomatch;
break;
case NACM_UPDATE:
/* 6f) For an "update" access operation, the rule's "access-operations"
leaf has the "update" bit set or has the special value "*". */
if (!match_access(access_operations, "update", "write"))
goto nomatch;
break;
default:
break;
}
/* Here module is matched, now check for path if any NYI */
if (path){
if ((xpath = xpath_first(xt, "%s", path)) == NULL)
goto nomatch;
/* The requested node xr is the node specified by the path or is a
* descendant node of the path:
* xmatch is one of xvec[] or an ancestor of the xvec[] nodes.
*/
xp = xr;
do {
if (xpath == xp)
goto match;
} while ((xp = xml_parent(xp)) != NULL);
}
match:
retval = 1;
done:
return retval;
nomatch:
retval = 0;
goto done;
}
/*! Go through all rules for a requested node
* @param[in] xt XML root tree with "config" label
* @param[in] xr Requested node (node in xt)
* @param[in] gvec NACM groups where user is member
* @param[in] glen Length of gvec
* @param[in] rlistvec NACM rule-list entries
* @param[in] rlistlen Length of rlistvec
* @param[out] xrulep If set, then points to matching rule
*/
static int
nacm_data_read_xr(cxobj *xt,
cxobj *xr,
cxobj **gvec,
size_t glen,
cxobj **rlistvec,
size_t rlistlen,
cxobj **xrulep)
{
int retval = -1;
int i, j;
cxobj *rlist;
char *gname;
cxobj **rvec = NULL; /* rules */
size_t rlen;
cxobj *xrule = NULL;
int match = 0;
for (i=0; i<rlistlen; i++){ /* Loop through rule list */
rlist = rlistvec[i];
/* Loop through user's group to find match in this rule-list */
for (j=0; j<glen; j++){
gname = xml_find_body(gvec[j], "name");
if (xpath_first(rlist, ".[group='%s']", gname)!=NULL)
break; /* found */
}
if (j==glen) /* not found */
continue;
/* 6. For each rule-list entry found, process all rules, in order,
until a rule that matches the requested access operation is
found. (see 6 sub rules in nacm_rule_datanode
*/
if (xpath_vec(rlist, "rule", &rvec, &rlen) < 0)
goto done;
for (j=0; j<rlen; j++){ /* Loop through rules */
xrule = rvec[j];
if ((match = nacm_rule_datanode(xt, xr, xrule, NACM_READ)) < 0)
goto done;
if (match) /* xrule match */
break;
}
if (rvec){
free(rvec);
rvec=NULL;
}
if (match) /* xrule match */
break;
}
if (match)
*xrulep = xrule;
else
*xrulep = NULL;
retval = 0;
done:
if (rvec)
free(rvec);
return retval;
}
/*! Make nacm datanode and module rule read access validation
* Just purge nodes that fail validation (dont send netconf error message)
* @param[in] xt XML root tree with "config" label
* @param[in] xrvec Vector of requested nodes (sub-part of xt)
* @param[in] xrlen Length of requsted node vector
* @param[in] username
* @param[in] xnacm NACM xml tree
* @retval -1 Error
* @retval 0 Not access and cbret set
* @retval 1 Access
* 3.2.4: <get> and <get-config> Operations
* Data nodes to which the client does not have read access are silently
* omitted, along with any descendants, from the <rpc-reply> message.
* For NETCONF filtering purposes, the selection criteria are applied to the
* subset of nodes that the user is authorized to read, not the entire datastore.
* @note assume mode is internal or external, not disabled
* @node There is unclarity on what "a data node" means wrt a read operation.
* Suppose a tree is accessed. Is "the data node" just the top of the tree?
* (1) Or is it all nodes, recursively, in the data-tree?
* (2) Or is the datanode only the requested tree, NOT the whole datatree?
* Example:
* - r0 default permit/deny *
* - rule r1 to permit/deny /a
* - rule r2 to permit/deny /a/b
* - rule r3 to permit/deny /a/b/c
* - rule r4 to permit/deny /d
* - read access on /a/b which returns <a><b><c/><d/></b></a>?
* permit - t; deny - f
* r1 | r2 | r3 | result (r0 and r4 are dont cares - dont match)
* ------+------+------+---------
* t | t | t | <a><b><c/><d/></b></a>
* t | t | f | <a><b><d/></b></a>
* t | f | t | <a/>
* t | f | f | <a/>
* f | t | t |
* f | t | f |
* f | f | t |
* f | f | f |
*
* - read access on / which returns <d/><e/>?
* permit - t; deny - f
* r0 | r4 | result
* ------+------+---------
* t | t | <d/><e/>
* t | f | <e/>
* f | t | <d/>
* f | f |
* Algorithm 1, based on an xml tree XT:
* The special variable ACTION can have values:
* permit/deny/default
* 1. Traverse through all nodes x in xt. Set ACTION to default
* - Find first exact matching rule r of non-default rules r1-rn on x
* - if found set ACTION to r->action (permit/deny).
* - if ACTION is deny purge x and all descendants
* - if ACTION is permit, mark x as PERMIT
* - continue traverse
* 2. If default action is
* 2. Traverse through all nodes x in xt. Set ACTION to default r0->action
* - if x is marked as PERMIT
* - if ACTION is deny deny purge x and all descendants
*
* Algorithm 2 (based on requested node set XRS which are sub-trees in XT).
* For each XR in XRS, check match with rule r1-rn, r0.
* 1. XR is PERMIT
* - Recursively match subtree to find reject sub-trees and purge.
* 2. XR is REJECT. Purge XR.
* Module-rule w no path is implicit rule on top node.
*
* A module rule has the "module-name" leaf set but no nodes from the
* "rule-type" choice set.
* @see RFC8341 3.4.5. Data Node Access Validation
* @see nacm_datanode_write
* @see nacm_rpc
*/
int
nacm_datanode_read(cxobj *xt,
cxobj **xrvec,
size_t xrlen,
char *username,
cxobj *xnacm)
{
int retval = -1;
cxobj **gvec = NULL; /* groups */
size_t glen;
cxobj *xr;
cxobj **rlistvec = NULL; /* rule-list */
size_t rlistlen;
int i;
char *read_default = NULL;
cxobj *xrule;
char *action;
/* 3. Check all the "group" entries to see if any of them contain a
"user-name" entry that equals the username for the session
making the request. (If the "enable-external-groups" leaf is
"true", add to these groups the set of groups provided by the
transport layer.) */
if (username == NULL)
goto step9;
/* User's group */
if (xpath_vec(xnacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0)
goto done;
/* 4. If no groups are found, continue with step 9. */
if (glen == 0)
goto step9;
/* 5. Process all rule-list entries, in the order they appear in the
configuration. If a rule-list's "group" leaf-list does not
match any of the user's groups, proceed to the next rule-list
entry. */
if (xpath_vec(xnacm, "rule-list", &rlistvec, &rlistlen) < 0)
goto done;
for (i=0; i<xrlen; i++){ /* Loop through requested nodes */
xr = xrvec[i]; /* requested node XR */
/* Loop through rule-list (steps 5,6,7) to find match of requested node
*/
xrule = NULL;
if (nacm_data_read_xr(xt, xr, gvec, glen, rlistvec, rlistlen,
&xrule) < 0)
goto done;
if (xrule){ /* xrule match requested node xr */
if ((action = xml_find_body(xrule, "action")) == NULL)
continue;
if (strcmp(action, "deny")==0){
if (xml_purge(xr) < 0)
goto done;
}
else if (strcmp(action, "permit")==0)
;/* XXX recursively find denies in xr and purge them
* ie call nacm_data_read_xr recursively?
*/
}
else{ /* no rule matching xr, apply default */
/*11. For a "read" access operation, if the "read-default" leaf is set
to "permit", then include the requested data node in the reply;
otherwise, do not include the requested data node or any of its
descendants in the reply.*/
read_default = xml_find_body(xnacm, "read-default");
if (read_default == NULL || strcmp(read_default, "deny")==0)
if (xml_purge(xr) < 0)
goto done;
}
} /* xr */
goto ok;
/* 8. At this point, no matching rule was found in any rule-list
entry. */
step9:
/* 9. For a "read" access operation, if the requested data node is
defined in a YANG module advertised in the server capabilities
and the data definition statement contains a
"nacm:default-deny-all" statement, then the requested data node
and all its descendants are not included in the reply.
*/
for (i=0; i<xrlen; i++) /* Loop through requested nodes */
if (xml_purge(xrvec[i]) < 0)
goto done;
ok:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (gvec)
free(gvec);
if (rlistvec)
free(rlistvec);
return retval;
}
/*! Make nacm datanode and module rule write access validation
* The operations of NACM are: create, read, update, delete, exec
* where write is short-hand for create+delete+update
* @param[in] xt XML root tree with "config" label. XXX?
* @param[in] xr XML requestor node (part of xt)
* @param[in] op NACM access of xr
* @param[in] username User making access
* @param[in] xnacm NACM xml tree
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
* @retval -1 Error
* @retval 0 Not access and cbret set
* @retval 1 Access
* @see RFC8341 3.4.4. Incoming RPC Message Validation
* @see RFC8341 3.4.5. Data Node Access Validation
* @see nacm_datanode_read
* @see nacm_rpc
*/
int
nacm_access(clicon_handle h,
char *rpc,
char *module,
char *username,
cbuf *cbret)
nacm_datanode_write(cxobj *xt,
cxobj *xr,
enum nacm_access access,
char *username,
cxobj *xnacm,
cbuf *cbret)
{
int retval = -1;
cxobj *xtop = NULL;
char *mode;
clicon_debug(1, "%s", __FUNCTION__);
mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (mode == NULL || strcmp(mode, "disabled") == 0){
retval = 1;
int retval = -1;
cxobj **gvec = NULL; /* groups */
size_t glen;
cxobj **rlistvec = NULL; /* rule-list */
size_t rlistlen;
cxobj *rlist;
cxobj **rvec = NULL; /* rules */
size_t rlen;
int i, j;
char *gname;
cxobj *xrule;
int match = 0;
char *action;
char *write_default;
if (xnacm == NULL)
goto permit;
/* 3. Check all the "group" entries to see if any of them contain a
"user-name" entry that equals the username for the session
making the request. (If the "enable-external-groups" leaf is
"true", add to these groups the set of groups provided by the
transport layer.) */
if (username == NULL)
goto step9;
/* User's group */
if (xpath_vec(xnacm, "groups/group[user-name='%s']", &gvec, &glen, username) < 0)
goto done;
}
/* 0. If nacm-mode is external, get NACM defintion from separet tree,
otherwise get it from internal configuration */
if (strcmp(mode, "external")==0){
if ((xtop = clicon_nacm_ext(h)) == NULL){
clicon_err(OE_XML, 0, "No nacm external tree");
/* 4. If no groups are found, continue with step 9. */
if (glen == 0)
goto step9;
/* 5. Process all rule-list entries, in the order they appear in the
configuration. If a rule-list's "group" leaf-list does not
match any of the user's groups, proceed to the next rule-list
entry. */
if (xpath_vec(xnacm, "rule-list", &rlistvec, &rlistlen) < 0)
goto done;
for (i=0; i<rlistlen; i++){
rlist = rlistvec[i];
/* Loop through user's group to find match in this rule-list */
for (j=0; j<glen; j++){
gname = xml_find_body(gvec[j], "name");
if (xpath_first(rlist, ".[group='%s']", gname)!=NULL)
break; /* found */
}
if (j==glen) /* not found */
continue;
if (xpath_vec(rlist, "rule", &rvec, &rlen) < 0)
goto done;
/* 6. For each rule-list entry found, process all rules, in order,
until a rule that matches the requested access operation is
found. (see 6 sub rules in nacm_rule_data_write)
*/
for (j=0; j<rlen; j++){ /* Loop through rules */
xrule = rvec[j];
if ((match = nacm_rule_datanode(xt, xr, xrule, access)) < 0)
goto done;
if (match) /* match */
break;
}
if (match)
break;
if (rvec){
free(rvec);
rvec = NULL;
}
}
else if (strcmp(mode, "internal")==0){
if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0)
goto done;
if (match){
if ((action = xml_find_body(xrule, "action")) == NULL)
goto step9;
if (strcmp(action, "deny")==0){
if (netconf_access_denied(cbret, "application", "access denied") < 0)
goto done;
goto deny;
}
else if (strcmp(action, "permit")==0)
goto permit;
}
else{
clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode);
goto done;
/* 8. At this point, no matching rule was found in any rule-list
entry. */
step9:
/* 10. For a "write" access operation, if the requested data node is
defined in a YANG module advertised in the server capabilities
and the data definition statement contains a
"nacm:default-deny-write" or a "nacm:default-deny-all"
statement, then the access request is denied for the data node
and all its descendants.
XXX
*/
/*12. For a "write" access operation, if the "write-default" leaf is
set to "permit", then permit the data node access request;
otherwise, deny the request.*/
write_default = xml_find_body(xnacm, "write-default");
if (write_default == NULL || strcmp(write_default, "permit") != 0){
if (netconf_access_denied(cbret, "application", "default deny") < 0)
goto done;
goto deny;
}
/* Do the real nacm processing */
if ((retval = nacm_rpc_validation(h, rpc, module, username, xtop, cbret)) < 0)
goto done;
permit:
retval = 1;
done:
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
if (strcmp(mode, "internal")==0 && xtop)
xml_free(xtop);
if (gvec)
free(gvec);
if (rlistvec)
free(rlistvec);
if (rvec)
free(rvec);
return retval;
deny: /* Here, cbret must contain a netconf error msg */
assert(cbuf_len(cbret));
retval = 0;
goto done;
}
/*---------------------------------------------------------------
* NACM pre-procesing
*/
/*! NACM intial pre- access control enforcements
* Initial NACM steps and common to all NACM access validation.
* If retval=0 continue with next NACM step, eg rpc, module,
* etc. If retval = 1 access is OK and skip next NACM step.
* @param[in] h Clicon handle
* @param[in] xnacm NACM XML tree, root should be "nacm"
* @param[in] username User name of requestor
* @retval -1 Error
* @retval 0 OK but not validated. Need to do NACM step using xnacm
* @retval 1 OK permitted. You do not need to do next NACM step
* @code
* if ((ret = nacm_access(mode, xnacm, username)) < 0)
* err;
* if (ret == 0){
* // Next step NACM processing
* xml_free(xnacm);
* }
* @endcode
* @see RFC8341 3.4 Access Control Enforcement Procedures
*/
int
nacm_access(char *mode,
cxobj *xnacm,
char *username)
{
int retval = -1;
cxobj *xnacm0 = NULL;
char *enabled;
cxobj *x;
clicon_debug(1, "%s", __FUNCTION__);
if (mode == NULL || strcmp(mode, "disabled") == 0)
goto permit;
/* 0. If nacm-mode is external, get NACM defintion from separet tree,
otherwise get it from internal configuration */
if (strcmp(mode, "external") && strcmp(mode, "internal")){
clicon_err(OE_XML, 0, "Invalid NACM mode: %s", mode);
goto done;
}
/* If config does not exist, then the operation is permitted. (?) */
if (xnacm == NULL)
goto permit;
/* Do initial nacm processing common to all access validation in
* RFC8341 3.4 */
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
operation is permitted. */
if ((x = xpath_first(xnacm, "enable-nacm")) == NULL)
goto permit;
enabled = xml_body(x);
if (strcmp(enabled, "true") != 0)
goto permit;
/* 2. If the requesting session is identified as a recovery session,
then the protocol operation is permitted. NYI */
if (username && strcmp(username, NACM_RECOVERY_USER) == 0)
goto permit;
retval = 0; /* not permitted yet. continue with next NACM step */
done:
if (retval != 0 && xnacm0)
xml_free(xnacm0);
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
return retval;
permit:
retval = 1;
goto done;
}
/*! NACM intial pre- access control enforcements
* Initial NACM steps and common to all NACM access validation.
* If retval=0 continue with next NACM step, eg rpc, module,
* etc. If retval = 1 access is OK and skip next NACM step.
* @param[in] h Clicon handle
* @param[in] username User name of requestor
* @param[out] xncam NACM XML tree, set if retval=0. Free after use
* @retval -1 Error
* @retval 0 OK but not validated. Need to do NACM step using xnacm
* @retval 1 OK permitted. You do not need to do next NACM step
* @code
* cxobj *xnacm = NULL;
* if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
* err;
* if (ret == 0){
* // Next step NACM processing
* xml_free(xnacm);
* }
* @endcode
* @see RFC8341 3.4 Access Control Enforcement Procedures
*/
int
nacm_access_pre(clicon_handle h,
char *username,
cxobj **xnacmp)
{
int retval = -1;
char *mode;
cxobj *x;
cxobj *xnacm0 = NULL;
cxobj *xnacm = NULL;
if ((mode = clicon_option_str(h, "CLICON_NACM_MODE")) != NULL){
if (strcmp(mode, "external")==0){
if ((x = clicon_nacm_ext(h)))
if ((xnacm0 = xml_dup(x)) == NULL)
goto done;
}
else if (strcmp(mode, "internal")==0){
if (xmldb_get(h, "running", "nacm", 0, &xnacm0) < 0)
goto done;
}
}
/* If config does not exist then the operation is permitted(?) */
if (xnacm0 == NULL)
goto permit;
/* If config does not exist then the operation is permitted(?) */
if ((xnacm = xpath_first(xnacm0, "nacm")) == NULL)
goto permit;
if (xml_rootchild_node(xnacm0, xnacm) < 0)
goto done;
xnacm0 = NULL;
if ((retval = nacm_access(mode, xnacm, username)) < 0)
goto done;
if (retval == 0){ /* if retval == 0 then return an xml nacm tree */
*xnacmp = xnacm;
xnacm = NULL;
}
done:
if (xnacm0)
xml_free(xnacm0);
else if (xnacm)
xml_free(xnacm);
return retval;
permit:
retval = 1;
goto done;
}

View file

@ -640,7 +640,7 @@ xml_child_each(cxobj *xparent,
cxobj *xprev,
enum cxobj_type type)
{
int i;
int i;
cxobj *xn = NULL;
for (i=xprev?xprev->_x_vector_i+1:0; i<xparent->x_childvec_len; i++){
@ -957,6 +957,7 @@ xml_rm(cxobj *xc)
* # Here xt will be: <a>2</a>
* @endcode
* @see xml_child_rm
* @see xml_child_rootchild_node where xc is explicitly given
*/
int
xml_rootchild(cxobj *xp,
@ -984,6 +985,44 @@ xml_rootchild(cxobj *xp,
return retval;
}
/*! Return a child sub-tree, while removing parent and all other children
* Given a root xml node, remove the child from its parent
* , remove the parent and all other children.
* Before: xp-->[..xc..]
* After: xc
* @param[in] xp xml parent node. Must be root. Will be deleted
* @param[in] xc xml child node. Must be a child of xp
* @retval 0 OK
* @retval -1 Error
* @see xml_child_rootchild where an index is used to find xc
*/
int
xml_rootchild_node(cxobj *xp,
cxobj *xc)
{
int retval = -1;
cxobj *x;
int i;
if (xml_parent(xp) != NULL){
clicon_err(OE_XML, 0, "Parent is not root");
goto done;
}
x = NULL; i = 0;
while ((x = xml_child_each(xp, x, -1)) != NULL) {
if (x == xc)
break;
i++;
}
if (xml_child_rm(xp, i) < 0)
goto done;
if (xml_free(xp) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Get the first sub-node which is an XML body.
* @param[in] xn xml tree node
* @retval The returned body as a pointer to the name string

View file

@ -379,6 +379,7 @@ xmldb_get(clicon_handle h,
* @param[in] db running or candidate
* @param[in] op Top-level operation, can be superceded by other op in tree
* @param[in] xt xml-tree. Top-level symbol is dummy
* @param[in] username User name for nacm
* @param[out] cbret Initialized cligen buffer. On exit contains XML if retval == 0
* @retval 1 OK
* @retval 0 Failed, cbret contains error xml message
@ -389,7 +390,7 @@ xmldb_get(clicon_handle h,
* cxobj *xret = NULL;
* if (xml_parse_string("<a>17</a>", yspec, &xt) < 0)
* err;
* if ((ret = xmldb_put(xh, "running", OP_MERGE, xt, cbret)) < 0)
* if ((ret = xmldb_put(xh, "running", OP_MERGE, xt, username, cbret)) < 0)
* err;
* if (ret==0)
* cbret contains netconf error message
@ -404,6 +405,7 @@ xmldb_put(clicon_handle h,
const char *db,
enum operation_type op,
cxobj *xt,
char *username,
cbuf *cbret)
{
int retval = -1;
@ -434,7 +436,7 @@ xmldb_put(clicon_handle h,
cbuf_free(cb);
}
#endif
retval = xa->xa_put_fn(xh, db, op, xt, cbret);
retval = xa->xa_put_fn(xh, db, op, xt, username, cbret);
done:
return retval;
}

View file

@ -2117,7 +2117,7 @@ api_path2xml(char *api_path,
int nvec;
cxobj *xroot;
clicon_debug(1, "%s", __FUNCTION__);
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
if (*api_path!='/'){
clicon_err(OE_XML, EINVAL, "Invalid api-path: %s (must start with '/')",
api_path);

View file

@ -1389,6 +1389,7 @@ ys_populate_identity(yang_stmt *ys,
/* Iterate through all base statements and check the base identity exists
* AND populate the base identity recursively
*/
yc = NULL;
while ((yc = yn_each((yang_node*)ys, yc)) != NULL) {
if (yc->ys_keyword != Y_BASE)
continue;

View file

@ -475,6 +475,7 @@ cv_validate1(cg_var *cv,
if (restype){
if (strcmp(restype, "enumeration") == 0){
found = 0;
yi = NULL;
while ((yi = yn_each((yang_node*)yrestype, yi)) != NULL){
if (yi->ys_keyword != Y_ENUM)
continue;
@ -500,6 +501,7 @@ cv_validate1(cg_var *cv,
if ((v = vec[i]) == NULL || !strlen(v))
continue;
found = 0;
yi = NULL;
while ((yi = yn_each((yang_node*)yrestype, yi)) != NULL){
if (yi->ys_keyword != Y_BIT)
continue;

View file

@ -7,6 +7,9 @@ application. Assumes setup of http daemon as describe under apps/restonf
- site.sh Add your site-specific modifications here
- test_nacm.sh Auth tests using internal NACM
- test_nacm_ext.sh Auth tests using external NACM (separate file)
- test_nacm_protocol.sh Auth tests for incoming RPC:s
- test_nacm_module_read.sh Auth tests for data node read operations
- test_nacm_module_write.sh Auth tests for data node write operations
- test_cli.sh CLI tests
- test_netconf.sh Netconf tests
- test_restconf.sh Restconf tests

View file

@ -149,14 +149,17 @@ expectfn(){
fi
}
#
expecteq(){
ret=$1
expect=$2
# echo "ret:$ret"
# echo "expect:$expect"
if [ -z "$ret" -a -z "$expect" ]; then
return
fi
if [[ "$ret" = "$expect" ]]; then
echo
echo
else
err "$expect" "$ret"
fi

View file

@ -96,7 +96,7 @@ new "minmax: empty"
expecteof "$clixon_netconf -qf $cfg" 0 '<rpc><edit-config><target><candidate/></target><default-operation>replace</default-operation><config><c xmlns="urn:example:clixon"/></config></edit-config></rpc>]]>]]>' '^<rpc-reply><ok/></rpc-reply>]]>]]>$'
# NYI
if false; then
if false; then # nyi
new "minmax: validate should fail"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"

View file

@ -9,20 +9,20 @@ APPNAME=example
. ./nacm.sh
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
fyangerr=$dir/err.yang
fyang=$dir/nacm-example.yang
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.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_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
@ -33,10 +33,13 @@ cat <<EOF > $cfg
EOF
cat <<EOF > $fyang
module $APPNAME{
module nacm-example{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
namespace "urn:example:nacm";
prefix nacm;
import clixon-example {
prefix ex;
}
import ietf-netconf-acm {
prefix nacm;
}
@ -52,7 +55,7 @@ EOF
RULES=$(cat <<EOF
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
<enable-nacm>false</enable-nacm>
<read-default>deny</read-default>
<read-default>permit</read-default>
<write-default>deny</write-default>
<exec-default>deny</exec-default>
@ -100,20 +103,20 @@ RULES=$(cat <<EOF
$NADMIN
</nacm>
<x xmlns="urn:example:clixon">0</x>
<x xmlns="urn:example:nacm">0</x>
EOF
)
new "test params: -f $cfg -y $fyang"
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
new "start backend -s init -f $cfg"
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
@ -124,22 +127,19 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf"
sleep 1
new "start restconf daemon (-a is enable basic authentication)"
sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data &
sudo su -c "$clixon_restconf -f $cfg -D $DBG -- -a" -s /bin/sh www-data &
sleep $RCWAIT
new "restconf DELETE whole datastore"
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ""
new2 "auth get"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" 'null
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 'null
'
new "auth set authentication config"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "commit it"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new2 "auth get (no user: access denied)"
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
@ -148,7 +148,7 @@ new2 "auth get (wrong passwd: access denied)"
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
new2 "auth get (access)"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 0}
'
#----------------Enable NACM
@ -157,24 +157,24 @@ new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" ""
new2 "admin get nacm"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 0}
'
new2 "limited get nacm"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0}
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 0}
'
new2 "guest get nacm"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "admin edit nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 1}' http://localhost/restconf/data/example:x)" ""
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" ""
new2 "limited edit nacm"
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new2 "guest edit nacm"
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"example:x": 3}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"

View file

@ -10,17 +10,16 @@ APPNAME=example
. ./nacm.sh
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
fyangerr=$dir/err.yang
fyang=$dir/nacm-example.yang
nacmfile=$dir/nacmfile
# Note filter out example_backend_nacm.so in CLICON_BACKEND_REGEXP below
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/example/yang</CLICON_YANG_DIR>
<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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
@ -39,13 +38,13 @@ cat <<EOF > $cfg
EOF
cat <<EOF > $fyang
module $APPNAME{
module nacm-example{
yang-version 1.1;
namespace "urn:example:my";
namespace "urn:example:nacm";
import clixon-example {
prefix ex;
}
prefix my;
prefix nacm;
container authentication {
description "Example code for enabling www basic auth and some example
users";
@ -77,7 +76,7 @@ EOF
cat <<EOF > $nacmfile
<nacm>
<enable-nacm>true</enable-nacm>
<read-default>deny</read-default>
<read-default>permit</read-default>
<write-default>deny</write-default>
<exec-default>deny</exec-default>
@ -124,9 +123,10 @@ cat <<EOF > $nacmfile
$NADMIN
</nacm>
<x xmlns="urn:example:nacm">0</x>
EOF
new "test params: -f $cfg -y $fyang"
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend -zf $cfg "
@ -135,9 +135,9 @@ if [ $BE -ne 0 ]; then
err
fi
sleep 1
new "start backend -s init -f $cfg -y $fyang"
new "start backend -s init -f $cfg"
# start new backend
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
@ -147,19 +147,16 @@ new "kill old restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
new "start restconf daemon (-a is enable http basic auth)"
sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data &
sudo su -c "$clixon_restconf -f $cfg -D $DBG -- -a" -s /bin/sh www-data &
sleep $RCWAIT
new "restconf DELETE whole datastore"
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ""
new2 "auth get"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" '{"clixon-example:state": {"op": "42"}}
'
new "Set x to 0"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 0}' http://localhost/restconf/data/example:x)" ""
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 0}' http://localhost/restconf/data/nacm-example:x)" ""
new2 "auth get (no user: access denied)"
expecteq "$(curl -sS -X GET -H \"Accept:\ application/yang-data+json\" http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
@ -168,46 +165,46 @@ new2 "auth get (wrong passwd: access denied)"
expecteq "$(curl -u andy:foo -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "The requested URL was unauthorized"}}} '
new2 "auth get (access)"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 0}
'
new2 "admin get nacm"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 0}
'
new2 "limited get nacm"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0}
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 0}
'
new2 "guest get nacm"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "admin edit nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"example:x": 1}' http://localhost/restconf/data/example:x)" ""
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-example:x)" ""
new2 "limited edit nacm"
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"x": 2}' http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new2 "guest edit nacm"
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"x": 3}' http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "cli show conf as admin"
expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang show conf" 0 "^x 1;$"
expectfn "$clixon_cli -1 -U andy -l o -f $cfg show conf" 0 "^x 1;$"
new "cli show conf as limited"
expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang show conf" 0 "^x 1;$"
expectfn "$clixon_cli -1 -U wilma -l o -f $cfg show conf" 0 "^x 1;$"
new "cli show conf as guest"
expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang show conf" 255 "protocol access-denied"
expectfn "$clixon_cli -1 -U guest -l o -f $cfg show conf" 255 "protocol access-denied"
new "cli rpc as admin"
expectfn "$clixon_cli -1 -U andy -l o -f $cfg -y $fyang rpc ipv4" 0 '<x xmlns="urn:example:clixon">ipv4</x><y xmlns="urn:example:clixon">42</y>'
expectfn "$clixon_cli -1 -U andy -l o -f $cfg rpc ipv4" 0 '<x xmlns="urn:example:clixon">ipv4</x><y xmlns="urn:example:clixon">42</y>'
new "cli rpc as limited"
expectfn "$clixon_cli -1 -U wilma -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied default deny"
expectfn "$clixon_cli -1 -U wilma -l o -f $cfg rpc ipv4" 255 "protocol access-denied default deny"
new "cli rpc as guest"
expectfn "$clixon_cli -1 -U guest -l o -f $cfg -y $fyang rpc ipv4" 255 "protocol access-denied access denied"
expectfn "$clixon_cli -1 -U guest -l o -f $cfg rpc ipv4" 255 "protocol access-denied access denied"
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"

292
test/test_nacm_module_read.sh Executable file
View file

@ -0,0 +1,292 @@
#!/bin/bash
# Authentication and authorization and IETF NACM
# NACM module rules
# A module rule has the "module-name" leaf set but no nodes from the
# "rule-type" choice set.
# @see test_nacm.sh is slightly modified - this follows the RFC more closely
# See RFC 8341 A.1 and A.2
# Note: use clixon-example instead of ietf-netconf-monitoring since the latter is
# Tests for
# deny-ncm: This rule prevents the "guest" group from reading any
# monitoring information in the "clixon-example" YANG
# module.
# permit-ncm: This rule allows the "limited" group to read the
# "clixon-example" YANG module.
# permit-exec: This rule allows the "limited" group to invoke any
# protocol operation supported by the server.
# permit-all: This rule allows the "admin" group complete access to
# all content in the server. No subsequent rule will match for the
# "admin" group because of this module rule
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
. ./nacm.sh
cfg=$dir/conf_yang.xml
fyang=$dir/nacm-example.yang
cat <<EOF > $cfg
<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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.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_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<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>
</config>
EOF
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 ($USER added in admin group)
# 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>deny</read-default>
<write-default>deny</write-default>
<exec-default>deny</exec-default>
$NGROUPS
<rule-list>
<name>guest-acl</name>
<group>guest</group>
<rule>
<name>permit-read</name>
<module-name>clixon-example</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-ncm</name>
<module-name>clixon-example</module-name>
<access-operations>read</access-operations>
<action>permit</action>
<comment>
Allow read access to the NETCONF monitoring information.
</comment>
</rule>
<rule>
<name>permit-exec</name>
<module-name>*</module-name>
<access-operations>exec</access-operations>
<action>permit</action>
<comment>
Allow invocation of the supported server operations.
</comment>
</rule>
</rule-list>
$NADMIN
</nacm>
<x xmlns="urn:example:nacm">42</x>
<translate xmlns="urn:example:clixon"><k>key42</k><value>val42</value></translate>
<translate xmlns="urn:example:clixon"><k>key43</k><value>val43</value></translate>
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"
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
sleep 1
new "start restconf daemon (-a is enable basic authentication)"
sudo su -c "$clixon_restconf -f $cfg -D $DBG -- -a" -s /bin/sh www-data &
sleep $RCWAIT
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 "commit it"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" ""
#--------------- nacm enabled
#----READ access
#user:admin
new2 "admin read ok"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" '{"clixon-example:translate": [{"k": "key42","value": "val42"},{ "k": "key43","value": "val43"}]}
'
new2 "admin read netconf ok"
expecteof "$clixon_netconf -U andy -qf $cfg" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/translate"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><translate xmlns="urn:example:clixon"><k>key42</k><value>val42</value></translate><translate xmlns="urn:example:clixon"><k>key43</k><value>val43</value></translate></data></rpc-reply>]]>]]>$'
new2 "admin read element ok"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" '{"clixon-example:value": "val42"}
'
new2 "admin read other module OK"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 42}
'
new2 "admin read state OK"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" '{"clixon-example:state": {"op": "42"}}
'
new "admin read top ok (all)"
ret=$(curl -u andy:bar -sS -X GET http://localhost/restconf/data)
expect='{"data": {"nacm-example:x": 42,"clixon-example:translate":'
match=`echo $ret | grep -EZo "$expect"`
if [ -z "$match" ]; then
err "$expect" "$ret"
fi
#user:limit
new2 "limit read ok"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" '{"clixon-example:translate": [{"k": "key42","value": "val42"},{ "k": "key43","value": "val43"}]}
'
new2 "limit read netconf ok"
expecteof "$clixon_netconf -U wilma -qf $cfg" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/translate"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><translate xmlns="urn:example:clixon"><k>key42</k><value>val42</value></translate><translate xmlns="urn:example:clixon"><k>key43</k><value>val43</value></translate></data></rpc-reply>]]>]]>$'
new2 "limit read element ok"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" '{"clixon-example:value": "val42"}
'
new2 "limit read other module fail"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 'null
'
new2 "limit read state OK"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" '{"clixon-example:state": {"op": "42"}}
'
new2 "limit read top ok (part)"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data)" '{"data": {"clixon-example:translate": [{"k": "key42","value": "val42"},{ "k": "key43","value": "val43"}],"clixon-example:state": {"op": "42"}}}
'
#user:guest
new2 "guest read fail"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new2 "guest read netconf fail"
expecteof "$clixon_netconf -U guest -qf $cfg" 0 '<rpc><get-config><source><candidate/></source><filter type="xpath" select="/translate"/></get-config></rpc>]]>]]>' '^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>default deny</error-message></rpc-error></rpc-reply>]]>]]>$'
new2 "guest read element fail"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate=key42/value)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new2 "guest read other module fail"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new2 "guest read state fail"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new2 "guest read top ok (part)"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
#------- RPC operation
new2 "admin rpc ok"
expecteq "$(curl -u andy:bar -s -X POST -d '{"clixon-example:input":{"x":42}}' http://localhost/restconf/operations/clixon-example:example)" '{"clixon-example:output": {"x": "42","y": "42"}}
'
new "admin rpc netconf ok"
expecteof "$clixon_netconf -U andy -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
new2 "limit rpc ok"
expecteq "$(curl -u wilma:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":42}}' )" '{"clixon-example:output": {"x": "42","y": "42"}}
'
new "limit rpc netconf ok"
expecteof "$clixon_netconf -U wilma -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><x xmlns="urn:example:clixon">0</x><y xmlns="urn:example:clixon">42</y></rpc-reply>]]>]]>$'
new2 "guest rpc fail"
expecteq "$(curl -u guest:bar -s -X POST http://localhost/restconf/operations/clixon-example:example -d '{"clixon-example:input":{"x":42}}' )" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "guest rpc netconf fail"
expecteof "$clixon_netconf -U guest -qf $cfg" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><example xmlns="urn:example:clixon"><x>0</x></example></rpc>]]>]]>' '^<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$'
#------------------ Set read-default permit
new "admin set read-default permit"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"read-default": "permit"}' http://localhost/restconf/data/ietf-netconf-acm:nacm/read-default)" ""
new2 "limit read ok"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/clixon-example:translate)" '{"clixon-example:translate": [{"k": "key42","value": "val42"},{ "k": "key43","value": "val43"}]}
'
new2 "limit read other module ok"
expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 42}
'
new2 "guest read state fail"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -eq 0 ]; then
exit # BE
fi
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
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
rm -rf $dir

274
test/test_nacm_module_write.sh Executable file
View file

@ -0,0 +1,274 @@
#!/bin/bash
# Authentication and authorization and IETF NACM
# NACM module rules
# A module rule has the "module-name" leaf set but no nodes from the
# "rule-type" choice set.
# @see test_nacm.sh is slightly modified - this follows the RFC more closely
# See RFC 8341 A.1 and A.2
# Note: use clixon-example instead of ietf-netconf-monitoring since the latter is
# A) Three tracks in the code for leaf/leaf-list, container/lists, and root
# B) Three operations: create, update, delete (write)
# C) Two access operations: permit, deny (also default deny/permit)
# This gives 18 testcases
# Set group access:
# - Admin: permit: create, update, delete
# - Limit: permit: create, delete; deny: update
# - Guest: permit: update; deny: create delete
# ops\track:| root | leaf | list
#-----------+--------+--------+----------
# create | na | p/d | p/d
# update | p/d | p/d | p/d
# delete | p/d | p/d | p/d
APPNAME=example
# include err() and new() functions and creates $dir
. ./lib.sh
. ./nacm.sh
cfg=$dir/conf_yang.xml
fyang=$dir/nacm-example.yang
cat <<EOF > $cfg
<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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.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_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<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>
</config>
EOF
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";
}
list a{
key k;
leaf k{
type string;
}
container b{
leaf c{
type string;
}
}
}
}
EOF
# The groups are slightly modified from RFC8341 A.1 ($USER added in admin group)
# The rule-list is from A.2
RULES=$(cat <<EOF
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
<enable-nacm>true</enable-nacm>
<read-default>deny</read-default>
<write-default>deny</write-default>
<exec-default>permit</exec-default>
$NGROUPS
<rule-list>
<name>limited-acl</name>
<group>limited</group>
<rule>
<name>permit-create-delete</name>
<module-name>nacm-example</module-name>
<access-operations>read create delete</access-operations>
<action>permit</action>
</rule>
<rule>
<name>deny-update</name>
<module-name>nacm-example</module-name>
<access-operations>read update</access-operations>
<action>deny</action>
</rule>
</rule-list>
<rule-list>
<name>guest-acl</name>
<group>guest</group>
<rule>
<name>permit-update</name>
<module-name>nacm-example</module-name>
<access-operations>read update</access-operations>
<action>permit</action>
</rule>
<rule>
<name>deny-create-delete</name>
<module-name>nacm-example</module-name>
<access-operations>read create delete</access-operations>
<action>deny</action>
</rule>
</rule-list>
$NADMIN
</nacm>
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"
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
fi
new "kill old restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
sleep 1
new "start restconf daemon (-a is enable basic authentication)"
sudo su -c "$clixon_restconf -f $cfg -D $DBG -- -a" -s /bin/sh www-data &
sleep $RCWAIT
# Set nacm from scratch
nacm(){
new "auth set authentication config"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config operation='replace'>$RULES</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>]]>]]>$"
new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" ""
}
#--------------- enable nacm
nacm
# ops\track:| root | leaf | list
#-----------+--------+--------+----------
# create | n/a | xp/dx | p/d
# update | p/d | xp/dx | p/d
# delete | p/d | xp/dx | p/d
#----------root
new2 "update root list default deny"
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d '<data><x xmlns="urn:example:nacm">42</x>$RULES</data>')" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
# replace all, then must include NACM rules as well
MSG="<data>$RULES</data>"
new "update root list permit"
expecteq "$(curl -u andy:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d "$MSG")" ''
new2 "delete root list deny"
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new "delete root permit"
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ''
#--------------- re-enable nacm
nacm
#----------leaf
new2 "create leaf deny"
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">42</x>')" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "create leaf permit"
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">42</x>')" ''
new2 "update leaf deny"
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">99</x>')" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "update leaf permit"
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:x -d '<x xmlns="urn:example:nacm">99</x>')" ''
new2 "read leaf check"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 99}
'
new2 "delete leaf deny"
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "delete leaf permit"
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:x)" ''
#----- list/container
new2 "create list deny"
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>str</c></b></a>')" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "create list permit"
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>str</c></b></a>')" ''
new2 "update list deny"
expecteq "$(curl -u wilma:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>update</c></b></a>')" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "update list permit"
expecteq "$(curl -u guest:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data/nacm-example:a=key42 -d '<a xmlns="urn:example:nacm"><k>key42</k><b><c>update</c></b></a>')" ''
new2 "read list check"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-example:a)" '{"nacm-example:a": [{"k": "key42","b": {"c": "update"}}]}
'
new2 "delete list deny"
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:a=key42)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} '
new "delete list permit"
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/nacm-example:a=key42)" ''
#----- default deny (clixon-example limit and guest have default access)
new2 "default create list deny"
expecteq "$(curl -u wilma:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k": "key42","value": "val42"}]}')" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new2 "create list permit"
expecteq "$(curl -u andy:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k": "key42","value": "val42"}]}')" ''
new2 "default update list deny"
expecteq "$(curl -u wilma:bar -sS -X PUT http://localhost/restconf/data/clixon-example:translate=key42 -d '{"clixon-example:translate": [{"k": "key42","value": "val99"}]}')" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new2 "default delete list deny"
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data/clixon-example:translate=key42)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"
if [ $BE -eq 0 ]; then
exit # BE
fi
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
sudo clixon_backend -z -f $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
rm -rf $dir

View file

@ -29,20 +29,20 @@ APPNAME=example
. ./nacm.sh
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
fyangerr=$dir/err.yang
fyang=$dir/nacm-example.yang
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.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_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
@ -53,10 +53,13 @@ cat <<EOF > $cfg
EOF
cat <<EOF > $fyang
module $APPNAME{
module nacm-example{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
namespace "urn:example:nacm";
prefix nacm;
import clixon-example {
prefix ex;
}
import ietf-netconf-acm {
prefix nacm;
}
@ -72,7 +75,7 @@ RULES=$(cat <<EOF
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
<enable-nacm>false</enable-nacm>
<read-default>deny</read-default>
<write-default>deny</write-default>
<write-default>permit</write-default>
<exec-default>deny</exec-default>
$NGROUPS
@ -122,21 +125,21 @@ RULES=$(cat <<EOF
$NADMIN
</nacm>
<x xmlns="urn:example:clixon">0</x>
<x xmlns="urn:example:nacm">0</x>
EOF
)
new "test params: -f $cfg -y $fyang"
new "test params: -f $cfg"
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
new "start backend -s init -f $cfg"
sudo $clixon_backend -s init -f $cfg -D $DBG
if [ $? -ne 0 ]; then
err
fi
@ -147,46 +150,48 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf"
sleep 1
new "start restconf daemon (-a is enable basic authentication)"
sudo su -c "$clixon_restconf -f $cfg -y $fyang -D $DBG -- -a" -s /bin/sh www-data &
sudo su -c "$clixon_restconf -f $cfg -D $DBG -- -a" -s /bin/sh www-data &
sleep $RCWAIT
new "auth set authentication config"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "commit it"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" ""
#--------------- nacm enabled
new2 "admin get nacm"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" '{"example:x": 0}
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" '{"nacm-example:x": 0}
'
# Rule 1: deny-kill-session
new "deny-kill-session: limited fail (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -U wilma" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
new "deny-kill-session: guest fail (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U guest" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -U guest" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
new "deny-kill-session: admin ok (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U andy" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -U andy" 0 "<rpc><kill-session><session-id>44</session-id></kill-session></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
# Rule 2: deny-delete-config
new "deny-delete-config: limited fail (netconf)"
expecteof "$clixon_netconf -qf $cfg -y $fyang -U wilma" 0 "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>protocol</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -U wilma" 0 "<rpc><delete-config><target><startup/></target></delete-config></rpc>]]>]]>" "^<rpc-reply><rpc-error><error-type>application</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>access denied</error-message></rpc-error></rpc-reply>]]>]]>$"
new2 "deny-delete-config: guest fail (restconf)"
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
expecteq "$(curl -u guest:bar -sS -X DELETE http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
# In restconf delete-config is translated to edit-config which is permitted
new "deny-delete-config: limited fail (restconf) ok"
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" ''
new2 "admin get nacm (should be null)"
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/example:x)" 'null
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-example:x)" 'null
'
new "deny-delete-config: admin ok (restconf)"
@ -194,20 +199,20 @@ expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ''
# Here the whole config is gone so we need to start again
new "auth set authentication config (restart)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "commit it"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm"
expecteq "$(curl -u andy:bar -sS -X PUT -d '{"ietf-netconf-acm:enable-nacm": true}' http://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" ""
# Rule 3: permit-edit-config
new "permit-edit-config: limited ok restconf"
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" ''
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" ''
new2 "permit-edit-config: guest fail restconf"
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"example:x": 2}' http://localhost/restconf/data/example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
expecteq "$(curl -u guest:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-example:x)" '{"ietf-restconf:errors" : {"error": {"error-type": "application","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} '
new "Kill restconf daemon"
sudo pkill -u www-data -f "/www-data/clixon_restconf"

View file

@ -143,22 +143,22 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><can
# LEAF_LISTS
new "add two entries to leaf-list user order"
new "add two entries (c,b) to leaf-list user order"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:clixon">c</y0><y0 xmlns="urn:example:clixon">b</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry to leaf-list user order"
new "add one entry (a) to leaf-list user order"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:clixon">a</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "add one entry to leaf-list user order after commit"
new "add one entry (0) to leaf-list user order after commit"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><candidate/></target><config><y0 xmlns="urn:example:clixon">0</y0></config></edit-config></rpc>]]>]]>' "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "verify leaf-list user order in running (as entered)"
new "verify leaf-list user order in running (as entered: c,b,a,0)"
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><get-config><source><running/></source><filter type="xpath" select="/y0"/></get-config></rpc>]]>]]>' '^<rpc-reply><data><y0 xmlns="urn:example:clixon">c</y0><y0 xmlns="urn:example:clixon">b</y0><y0 xmlns="urn:example:clixon">a</y0><y0 xmlns="urn:example:clixon">0</y0></data></rpc-reply>]]>]]>$'
# LISTS

View file

@ -146,7 +146,9 @@ module clixon-config {
leaf CLICON_YANG_MAIN_FILE {
type string;
description
"If specified load a yang module in a specific absolute filename";
"If specified load a yang module in a specific absolute filename.
This corresponds to the -y command-line option in most CLixon
programs.";
}
leaf CLICON_YANG_MAIN_DIR {
type string;