* Top-level default leafs assigned.

* Enforcing RFC 7950 Sec 7.6.1 means unassigned top-level leafs (or leafs under non-presence containers) are assigned default values.
* NACM default behaviour is read-only (empty configs are dead-lockedd)
  * This applies if NACM is loaded and `CLICON_NACM_MODE` is `internal`
* Fixed: [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111)
This commit is contained in:
Olof hagsand 2020-08-06 15:19:38 +02:00
parent 65733ffe69
commit 794d51a365
30 changed files with 593 additions and 167 deletions

View file

@ -42,6 +42,34 @@ Expected: July 2020
### API changes on existing protocol/config features (For users)
* Top-level default leafs assigned.
* Enforcing RFC 7950 Sec 7.6.1 means unassigned top-level leafs (or leafs under non-presence containers) are assigned default values.
* In this process non-presence containers may be created.
* See also [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111).
* NACM default behaviour is read-only (empty configs are dead-lockedd)
* This applies if NACM is loaded and `CLICON_NACM_MODE` is `internal`
* Due to the previous bult (top-level default leafs)
* This means that empty configs or empty NACM configs are not writable (deadlocked).
* Workarounds:
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
* This means that empty configs or empty NACM configs are not writable (deadlocked).
* Workarounds:
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
* This means that empty configs or empty NACM configs are not writable (deadlocked).
* Workarounds:
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
* This means that empty configs or empty NACM configs are not writable (deadlocked).
* Workarounds:
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
* This means that empty configs or empty NACM configs are not writable (deadlocked).
* Workarounds:
1. Access the system with the recovery user, see clixon option CLICON_NACM_RECOVERY_USER
2. Edit the startup-db with a valid NACM config and restart the system
3. Set clixon option CLICON_NACM_DISABLED_ON_EMPTY to true which means that if the config is (completely) empty, you can add a NACM config as a first edit.
* Netconf lock/unlock behaviour changed to adhere to RFC 6241
* Changed commit lock error tag from "lock denied" to "in-use".
* Changed unlock error message from "lock is already held" to #lock not active" or "lock held by other session".
@ -53,6 +81,7 @@ Expected: July 2020
* CLICON_SSL_SERVER_CERT
* CLICON_SSL_SERVER_KEY
* CLICON_SSL_CA_CERT
* Added CLICON_NACM_DISABLED_ON_EMPTY to mitigate read-only "dead-lock" of empty startup configs.
* Restconf FCGI (eg via nginx) have changed reply message syntax slightly as follows (due to refactoring and common code with evhtp):
* Bodies in error retuns including html code have been removed
* Some (extra) CRLF:s have been removed
@ -99,6 +128,8 @@ Expected: July 2020
### Corrected Bugs
* Fixed: [default values don't show up in datastores #111](https://github.com/clicon/clixon/issues/111).
* See also API changes since this changes NACM behavior for example.
* Fixed: Don't call upgrade callbacks if no revision defined so there's no way to determine right way 'from' and 'to'
* Fixed: [lock candidate succeeded even though it is modified #110](https://github.com/clicon/clixon/issues/110)
* Fixed: [Need to add the possibility to use anchors around patterns #51](https://github.com/clicon/cligen/issues/51):

View file

@ -238,13 +238,13 @@ startup_extraxml(clicon_handle h,
goto fail;
}
/*
* Check if tmp db is empty.
* Check if tmp db is empty. XXX no this is not possible.
* It should be empty if extra-xml is null and reset plugins did nothing
* then skip validation.
*/
if (xmldb_get(h, tmp_db, NULL, NULL, &xt0) < 0)
goto done;
if (xt0==NULL || xml_child_nr(xt0)==0)
if (xmldb_empty_get(h, tmp_db))
goto ok;
xt = NULL;
/* Validate the tmp db and return possibly upgraded xml in xt

View file

@ -54,7 +54,8 @@
typedef struct {
uint32_t de_id; /* session id */
cxobj *de_xml; /* cache */
int de_modified;
int de_modified; /* Dirty since loaded/copied/committed/etc XXX:nocache? */
int de_empty; /* Empty on read from file, xmldb_readfile and xmldb_put sets it */
} db_elmnt;
/*

View file

@ -73,7 +73,7 @@ cxobj *xmldb_cache_get(clicon_handle h, const char *db);
int xmldb_modified_get(clicon_handle h, const char *db);
int xmldb_modified_set(clicon_handle h, const char *db, int value);
int xmldb_empty_get(clicon_handle h, const char *db);
int xmldb_dump(clicon_handle h, FILE *f, cxobj *xt);
#endif /* _CLIXON_DATASTORE_H */

View file

@ -66,6 +66,9 @@ int xml_tree_prune_flagged(cxobj *xt, int flag, int test);
int xml_namespace_change(cxobj *x, char *ns, char *prefix);
int xml_default(cxobj *x);
int xml_default_recurse(cxobj *xn);
int xml_default_yspec(yang_stmt *yspec, cxobj *xn);
int xml_nopresence_default(cxobj *xt);
int xml_nopresence_default_mark(cxobj *x, void *arg);
int xml_sanity(cxobj *x, void *arg);
int xml_non_config_data(cxobj *xt, void *arg);

View file

@ -49,7 +49,8 @@
/* Struct containing module state differences between two modules or two
* revisions of same module.
* This is in state of flux so it needs to be contained and easily changed.
* The most significant usecase is one module-state is a loaded datastore and the other
* is the one loaded by the server by its YANG files.
*/
typedef struct {
int md_status; /* 0 if no module-state in a datastore, 1 if there is */

View file

@ -636,12 +636,11 @@ clicon_argv_set(clicon_handle h,
return retval;
}
/*! Get xml database element including pid and xml cache
/*! Get xml database element including id, xml cache, empty on startup and dirty bit
* @param[in] h Clicon handle
* @param[in] db Name of database
* @retval de Database element
* @retval NULL None found
* @note these use db_elmnt hash, not data
*/
db_elmnt *
clicon_db_elmnt_get(clicon_handle h,
@ -655,14 +654,12 @@ clicon_db_elmnt_get(clicon_handle h,
return NULL;
}
/*! Set xml database element including pid and xml cache
/*! Set xml database element including id, xml cache, empty on startup and dirty bit
* @param[in] h Clicon handle
* @param[in] db Name of database
* @param[in] de Database element
* @retval 0 OK
* @retval -1 Error
* XXX add prefix to db to ensure uniqueness?
* @note these use db_elmnt hash, not data
*/
int
clicon_db_elmnt_set(clicon_handle h,

View file

@ -518,6 +518,26 @@ xmldb_modified_get(clicon_handle h,
return de->de_modified;
}
/*! Get empty flag from datastore (the datastore was empty ON LOAD)
* @param[in] h Clicon handle
* @param[in] db Database name
* @retval -1 Error (datastore does not exist)
* @retval 0 Db was not empty on load
* @retval 1 Db was empty on load
*/
int
xmldb_empty_get(clicon_handle h,
const char *db)
{
db_elmnt *de;
if ((de = clicon_db_elmnt_get(h, db)) == NULL){
clicon_err(OE_CFG, EFAULT, "datastore %s does not exist", db);
return -1;
}
return de->de_empty;
}
/*! Get modified flag from datastore
* @param[in] h Clicon handle
* @param[in] db Database name

View file

@ -72,6 +72,7 @@
#include "clixon_xpath.h"
#include "clixon_json.h"
#include "clixon_nacm.h"
#include "clixon_path.h"
#include "clixon_netconf_lib.h"
#include "clixon_yang_module.h"
#include "clixon_xml_map.h"
@ -443,25 +444,55 @@ text_read_modstate(clicon_handle h,
return retval;
}
static int
disable_nacm_on_empty(cxobj *x0,
yang_stmt *yspec)
{
int retval = -1;
yang_stmt *ymod;
cxobj **vec = NULL;
int len = 0;
cxobj *xb;
if ((ymod = yang_find(yspec, Y_MODULE, "ietf-netconf-acm")) == NULL)
goto ok;
if (clixon_xml_find_instance_id(x0, yspec, &vec, &len, "/nacm:nacm/nacm:enable-nacm") < 1)
goto done;
if (len){
if ((xb = xml_body_get(vec[0])) == NULL)
goto done;
if (xml_value_set(xb, "false") < 0)
goto done;
}
if (vec)
free(vec);
ok:
retval = 0;
done:
return retval;
}
/*! Common read function that reads an XML tree from file
* @param[in] th Datastore text handle
* @param[in] db Symbolic database name, eg "candidate", "running"
* @param[in] yb How to bind yang to XML top-level when parsing
* @param[in] yspec Top-level yang spec
* @param[out] xp XML tree read from file
* @param[out] de If set, return db-element status (eg empty flag)
* @param[out] msdiff If set, return modules-state differences
* @retval -1 Error
* @retval 0 Parse OK but yang assigment not made (or only partial) and xerr set
* @retval 1 OK
* @note retval 0 is NYI becaues of functions calling this function
* @note retval 0 is NYI because of functions calling this function cannot handle it yet
*/
#undef XMLDB_READFILE_FAIL
#undef XMLDB_READFILE_FAIL /* See comment on retval = 0 above */
int
xmldb_readfile(clicon_handle h,
const char *db,
yang_bind yb,
yang_stmt *yspec,
cxobj **xp,
db_elmnt *de,
modstate_diff_t *msdiff)
{
int retval = -1;
@ -497,8 +528,9 @@ xmldb_readfile(clicon_handle h,
goto fail;
#endif
/* Always assert a top-level called "config".
To ensure that, deal with two cases:
1. File is empty <top/> -> rename top-level to "config" */
* To ensure that, deal with two cases:
* 1. File is empty <top/> -> rename top-level to "config"
*/
if (xml_child_nr(x0) == 0){
if (xml_name_set(x0, "config") < 0)
goto done;
@ -509,7 +541,10 @@ xmldb_readfile(clicon_handle h,
if (singleconfigroot(x0, &x0) < 0)
goto done;
}
/* From Clixon 3.10,datastore files may contain module-state defining
if (xml_child_nr(x0) == 0 && de)
de->de_empty = 1;
/* Datastore files may contain module-state defining
* which modules are used in the file.
*/
if (text_read_modstate(h, yspec, x0, msdiff) < 0)
@ -569,16 +604,36 @@ xmldb_get_nocache(clicon_handle h,
size_t xlen;
int i;
int ret;
db_elmnt de0 = {0,};
int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
goto done;
}
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &xt, msdiff)) < 0)
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &xt, &de0, msdiff)) < 0)
goto done;
if (ret == 0)
goto fail;
clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
/* Check if empty */
if (xml_child_nr(xt) == 0)
empty = 1;
/* Add global defaults.
* Must do it before xpath check, since globals may be filtered out
*/
if (xml_default_yspec(yspec, xt) < 0)
goto done;
/* If empty database, then disable NACM if loaded
* This has some drawbacks. One is that a config may become empty at a later stage
* and then this does not hold.
*/
if (empty &&
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
if (disable_nacm_on_empty(xt, yspec) < 0)
goto done;
}
/* Here xt looks like: <config>...</config> */
/* Given the xpath, return a vector of matches in xvec */
if (xpath_vec(xt, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
@ -664,6 +719,7 @@ xmldb_get_cache(clicon_handle h,
cxobj *x1t = NULL;
db_elmnt de0 = {0,};
int ret;
int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
@ -672,7 +728,7 @@ xmldb_get_cache(clicon_handle h,
de = clicon_db_elmnt_get(h, db);
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
/* If there is no xml x0 tree (in cache), then read it from file */
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, msdiff)) < 0)
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, &de0, msdiff)) < 0)
goto done;
if (ret == 0)
goto fail;
@ -680,10 +736,30 @@ xmldb_get_cache(clicon_handle h,
* No, argument against: we may want to have a semantically wrong file and wish to edit?
*/
de0.de_xml = x0t;
clicon_db_elmnt_set(h, db, &de0);
clicon_db_elmnt_set(h, db, &de0); /* Content is copied */
} /* x0t == NULL */
else
x0t = de->de_xml;
/* Check if empty, must be before add global defaults */
if (xml_child_nr(x0t) == 0)
empty = 1;
/* Add global defaults.
* Cant do it to x1t since that is after xpath check, since globals may be filtered out
*/
if (xml_default_yspec(yspec, x0t) < 0)
goto done;
/* If empty database, then disable NACM if loaded
* This has some drawbacks. One is that a config may become empty at a later stage
* and then this does not hold.
*/
if (empty &&
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
if (disable_nacm_on_empty(x0t, yspec) < 0)
goto done;
}
/* Here x0t looks like: <config>...</config> */
/* Given the xpath, return a vector of matches in xvec
* Can we do everything in one go?
@ -693,8 +769,6 @@ xmldb_get_cache(clicon_handle h,
* a) for every node that is found, copy to new tree
* b) if config dont dont state data
*/
/* Here xt looks like: <config>...</config> */
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
@ -703,6 +777,7 @@ xmldb_get_cache(clicon_handle h,
goto done;
xml_spec_set(x1t, xml_spec(x0t));
if (xlen < 1000){
/* This is optimized for the case when the tree is large and xlen is small
* If the tree is large and xlen too, then the other is better.
@ -786,6 +861,7 @@ xmldb_get_zerocopy(clicon_handle h,
db_elmnt *de = NULL;
db_elmnt de0 = {0,};
int ret;
int empty = 0;
if ((yspec = clicon_dbspec_yang(h)) == NULL){
clicon_err(OE_YANG, ENOENT, "No yang spec");
@ -794,7 +870,7 @@ xmldb_get_zerocopy(clicon_handle h,
de = clicon_db_elmnt_get(h, db);
if (de == NULL || de->de_xml == NULL){ /* Cache miss, read XML from file */
/* If there is no xml x0 tree (in cache), then read it from file */
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, msdiff)) < 0)
if ((ret = xmldb_readfile(h, db, yb, yspec, &x0t, &de0, msdiff)) < 0)
goto done;
if (ret == 0)
goto fail;
@ -806,6 +882,25 @@ xmldb_get_zerocopy(clicon_handle h,
} /* x0t == NULL */
else
x0t = de->de_xml;
/* Check if empty, must be before add global defaults */
if (xml_child_nr(x0t) == 0)
empty = 1;
/* Add global defaults.
* Cant do it to x1t since that is after xpath check, since globals may be filtered out
*/
if (xml_default_yspec(yspec, x0t) < 0)
goto done;
/* If empty database, then disable NACM if loaded
* This has some drawbacks. One is that a config may become empty at a later stage
* and then this does not hold.
*/
if (empty &&
clicon_option_bool(h, "CLICON_NACM_DISABLED_ON_EMPTY")){
if (disable_nacm_on_empty(x0t, yspec) < 0)
goto done;
}
/* Here xt looks like: <config>...</config> */
if (xpath_vec(x0t, nsc, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
goto done;
@ -944,12 +1039,16 @@ xmldb_get0_clear(clicon_handle h,
if (x == NULL)
goto ok;
/* clear XML tree of defaults */
/* Mark non-presence containers as XML_FLAG_DEFAULT */
if (xml_apply(x, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
goto done;
/* Clear XML tree of defaults */
if (xml_tree_prune_flagged(x, XML_FLAG_DEFAULT, 1) < 0)
goto done;
/* clear mark and change */
xml_apply0(x, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset,
(void*)(0xff));
(void*)(0xffff));
ok:
retval = 0;
done:
@ -972,3 +1071,4 @@ xmldb_get0_free(clicon_handle h,
*xp = NULL;
return 0;
}

View file

@ -39,6 +39,7 @@
/*
* Prototypes
*/
int xmldb_readfile(clicon_handle h, const char *db, yang_bind yb, yang_stmt *yspec, cxobj **xp, modstate_diff_t *msd);
int xmldb_readfile(clicon_handle h, const char *db, yang_bind yb, yang_stmt *yspec,
cxobj **xp, db_elmnt *de, modstate_diff_t *msd);
#endif /* _CLIXON_DATASTORE_READ_H */

View file

@ -481,10 +481,12 @@ text_modify(clicon_handle h,
switch(op){
case OP_CREATE:
if (x0){
if (xml_nopresence_default(x0) == 0){
if (netconf_data_exists(cbret, "Data already exists; cannot create new resource") < 0)
goto done;
goto fail;
}
}
case OP_REPLACE: /* fall thru */
case OP_MERGE:
if (!(op == OP_MERGE && instr==NULL)){
@ -928,7 +930,7 @@ xmldb_put(clicon_handle h,
/* If there is no xml x0 tree (in cache), then read it from file */
if (x0 == NULL){
firsttime++; /* to avoid leakage on error, see fail from text_modify */
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, NULL)) < 0)
if ((ret = xmldb_readfile(h, db, YB_MODULE, yspec, &x0, de, NULL)) < 0)
goto done;
if (ret == 0)
goto fail;
@ -987,11 +989,11 @@ xmldb_put(clicon_handle h,
db_elmnt de0 = {0,};
if (de != NULL)
de0 = *de;
if (de0.de_xml == NULL){
if (de0.de_xml == NULL)
de0.de_xml = x0;
de0.de_empty = (xml_child_nr(de0.de_xml) == 0);
clicon_db_elmnt_set(h, db, &de0);
}
}
if (xmldb_db2file(h, db, &dbfile) < 0)
goto done;
if (dbfile==NULL){
@ -1057,6 +1059,9 @@ xmldb_dump(clicon_handle h,
char *format;
int pretty;
/* clear XML tree of defaults */
if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
goto done;
/* Add modstate first */
if ((x = clicon_modst_cache_get(h, 1)) != NULL){
if ((xmodst = xml_dup(x)) == NULL)
@ -1079,3 +1084,4 @@ xmldb_dump(clicon_handle h,
done:
return retval;
}

View file

@ -1100,7 +1100,9 @@ nacm_access(clicon_handle h,
/* 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. */
* operation is permitted.
* note option CLICON_NACM_DISABLED_ON_EMPTY
*/
if ((x = xpath_first(xnacm, nsc, "enable-nacm")) == NULL)
goto permit;
enabled = xml_body(x);
@ -1155,6 +1157,7 @@ nacm_access_pre(clicon_handle h,
cxobj *xnacm = NULL;
cvec *nsc = NULL;
/* Check clixon option: disabled, external tree or internal */
mode = clicon_option_str(h, "CLICON_NACM_MODE");
if (mode == NULL)
goto permit;

View file

@ -1883,6 +1883,7 @@ xml_copy_one(cxobj *x0,
default:
break;
}
xml_flag_set(x1, xml_flag(x0, XML_FLAG_DEFAULT)); /* Maybe more flags */
retval = 0;
done:
return retval;

View file

@ -979,40 +979,18 @@ xml_namespace_change(cxobj *x,
return retval;
}
/*! Add default values (if not set)
* @param[in] xt XML tree with some node marked
* Typically called in a recursive apply function:
* @retval 0 OK
* @retval -1 Error
*/
int
xml_default(cxobj *xt)
xml_default_create1(yang_stmt *y,
cxobj *xt,
int top,
cxobj **xcp)
{
int retval = -1;
yang_stmt *ys;
yang_stmt *y;
// int i; // XXX
cxobj *xc;
cxobj *xb;
char *str;
int added=0;
char *namespace;
char *prefix;
int ret;
cxobj *xc = NULL;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
/* Check leaf defaults */
if (yang_keyword_get(ys) == Y_CONTAINER || yang_keyword_get(ys) == Y_LIST ||
yang_keyword_get(ys) == Y_INPUT){
y = NULL;
while ((y = yn_each(ys, y)) != NULL) {
if (yang_keyword_get(y) != Y_LEAF)
continue;
if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
if (!xml_find(xt, yang_argument_get(y))){
if ((xc = xml_new(yang_argument_get(y), NULL, CX_ELMNT)) == NULL)
goto done;
xml_spec_set(xc, y);
@ -1022,15 +1000,18 @@ xml_default(cxobj *xt)
prefix = NULL;
if ((ret = xml2prefix(xt, namespace, &prefix)) < 0)
goto done;
if (ret){
if (ret){ /* Namespace found, prefix returned in prefix */
if (xml_prefix_set(xc, prefix) < 0)
goto done;
}
else{ /* namespace does not exist in target, use source prefix */
else{ /* Namespace does not exist in target, must add it w xmlns attr.
use source prefix */
if (!top){
if ((prefix = yang_find_myprefix(y)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
}
if (add_namespace(xc, xc, prefix, namespace) < 0)
goto done;
/* Add prefix to x, if any */
@ -1038,7 +1019,34 @@ xml_default(cxobj *xt)
goto done;
}
}
if (xml_addsub(xt, xc) < 0)
goto done;
*xcp = xc;
retval = 0;
done:
return retval;
}
/*! Create leaf from default value
*
* @param[in] yt Yang spec
* @param[in] xt XML tree
* @param[in] top Use default namespace (if you create xmlns statement)
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_default_create(yang_stmt *y,
cxobj *xt,
int top)
{
int retval = -1;
cxobj *xc = NULL;
cxobj *xb;
char *str;
if (xml_default_create1(y, xt, top, &xc) < 0)
goto done;
xml_flag_set(xc, XML_FLAG_DEFAULT);
if ((xb = xml_new("body", xc, CX_BODY)) == NULL)
goto done;
@ -1049,20 +1057,169 @@ xml_default(cxobj *xt)
if (xml_value_set(xb, str) < 0)
goto done;
free(str);
added++;
if (xml_insert(xt, xc, INS_LAST, NULL, NULL) < 0)
goto done;
}
}
}
}
retval = 0;
done:
return retval;
}
/*! Recursively fill in default values in a tree
* Alt to use xml_apply
/*! Try to see if intermediate nodes are necessary for default values, create if so
*
* @param[in] yt Yang container (no-presence)
* @param[out] createp Need to create XML container
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_nopresence_try(yang_stmt *yt,
int *createp)
{
int retval = -1;
yang_stmt *y;
if (yt == NULL || yang_keyword_get(yt) != Y_CONTAINER){
clicon_err(OE_XML, EINVAL, "yt argument is not container");
goto done;
}
*createp = 0;
y = NULL;
while ((y = yn_each(yt, y)) != NULL) {
switch (yang_keyword_get(y)){
case Y_LEAF:
if (!cv_flag(yang_cv_get(y), V_UNSET)){ /* Default value exists */
/* Need to create container */
*createp = 1;
goto ok;
}
break;
case Y_CONTAINER:
if (yang_find(y, Y_PRESENCE, NULL) == NULL){
/* If this is non-presence, (and it does not exist in xt) call recursively
* and create nodes if any default value exist first. Then continue and populate?
*/
if (xml_nopresence_try(y, createp) < 0)
goto done;
if (*createp)
goto ok;
}
break;
default:
break;
} /* switch */
}
ok:
retval = 0;
done:
return retval;
}
/*! Ensure default values are set on (children of) one single xml node
*
* Not recursive, except in one case with one or several non-presence containers, in which case
* XML containers may be created to host default values. That code may be a little too recursive.
* @param[in] yt Yang spec
* @param[in] xt XML tree (with yt as spec of xt, informally)
* @retval 0 OK
* @retval -1 Error
*/
static int
xml_default1(yang_stmt *yt,
cxobj *xt)
{
int retval = -1;
yang_stmt *yc;
cxobj *xc;
int top=0; /* Top symbol (set default namespace) */
int create = 0;
if (xt == NULL){ /* No xml */
clicon_err(OE_XML, EINVAL, "No XML argument");
goto done;
}
switch (yang_keyword_get(yt)){
case Y_MODULE:
case Y_SUBMODULE:
top++;
case Y_CONTAINER: /* XXX maybe check for non-presence here as well */
case Y_LIST:
case Y_INPUT:
case Y_OUTPUT:
yc = NULL;
while ((yc = yn_each(yt, yc)) != NULL) {
switch (yang_keyword_get(yc)){
case Y_LEAF:
if (!cv_flag(yang_cv_get(yc), V_UNSET)){ /* Default value exists */
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such child exist, create this leaf */
if (xml_default_create(yc, xt, top) < 0)
goto done;
xml_sort(xt);
}
}
break;
case Y_CONTAINER:
if (yang_find(yc, Y_PRESENCE, NULL) == NULL){
/* If this is non-presence, (and it does not exist in xt) call
* recursively and create nodes if any default value exist first.
* Then continue and populate?
*/
if (xml_find_type(xt, NULL, yang_argument_get(yc), CX_ELMNT) == NULL){
/* No such container exist, recursively try if needed */
if (xml_nopresence_try(yc, &create) < 0)
goto done;
if (create){
/* Retval shows there is a default value need to create the
* container */
if (xml_default_create1(yc, xt, top, &xc) < 0)
goto done;
xml_sort(xt);
/* Then call it recursively */
if (xml_default1(yc, xc) < 0)
goto done;
}
}
}
break;
default:
break;
}
}
break;
default:
break;
} /* switch */
retval = 0;
done:
return retval;
}
/*! Ensure default values are set on existing leaf children of this node
*
* Assume yang is bound to the tree
* @param[in] xt XML tree
* @retval 0 OK
* @retval -1 Error
*/
int
xml_default(cxobj *xt)
{
int retval = -1;
yang_stmt *ys;
if ((ys = (yang_stmt*)xml_spec(xt)) == NULL){
retval = 0;
goto done;
}
if (xml_default1(ys, xt) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! Recursively fill in default values in an XML tree
* @param[in] xt XML tree
* @retval 0 OK
* @retval -1 Error
*/
int
xml_default_recurse(cxobj *xn)
@ -1082,6 +1239,85 @@ xml_default_recurse(cxobj *xn)
return retval;
}
/*! Ensure default values are set on top-level
*
* Not recursive, except in one case with one or several non-presence containers
* @param[in] xt XML tree
* Typically called in a recursive apply function:
* @retval 0 OK
* @retval -1 Error
*/
int
xml_default_yspec(yang_stmt *yspec,
cxobj *xt)
{
int retval = -1;
yang_stmt *ymod = NULL;
if (yspec == NULL || yang_keyword_get(yspec) != Y_SPEC){
clicon_err(OE_XML, EINVAL, "yspec argument is not yang spec");
goto done;
}
while ((ymod = yn_each(yspec, ymod)) != NULL)
if (xml_default1(ymod, xt) < 0)
goto done;
retval = 0;
done:
return retval;
}
/*! This node is a default set value or (recursively) a non-presence container
* @retval 1 xt is a nopresence/default node (ie "virtual")
* @retval 0 xt is not such a node
*/
int
xml_nopresence_default(cxobj *xt)
{
cxobj *xc;
yang_stmt *yt;
if ((yt = xml_spec(xt)) == NULL)
return 0;
switch (yang_keyword_get(yt)){
case Y_CONTAINER:
if (yang_find(yt, Y_PRESENCE, NULL))
return 0;
break;
case Y_LEAF:
return xml_flag(xt, XML_FLAG_DEFAULT)?1:0;
break;
default:
return 0;
}
xc = NULL;
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) {
if (xml_nopresence_default(xc) == 0)
return 0;
}
return 1;
}
/*! Remove xml container if it is non-presence and only contains default leafs
* Called from xml_apply. Reason for marking is to delete it afterwords.
* @param[in] x
* @param[in] arg (flag value)
* @code
* if (xml_apply(xt, CX_ELMNT, xml_nopresence_default_mark, (void*)XML_FLAG_DEFAULT) < 0)
* err;
* if (xml_tree_prune_flagged(xt, XML_FLAG_DEFAULT, 1) < 0)
* goto done;
* @endcode
*/
int
xml_nopresence_default_mark(cxobj *x,
void *arg)
{
if (xml_nopresence_default(x))
xml_flag_set(x, (intptr_t)arg);
return 0;
}
/*! Sanitize an xml tree: xml node has matching yang_stmt pointer
* @param[in] xt XML top of tree
*/

View file

@ -1029,6 +1029,7 @@ xml_insert2(cxobj *xp,
* @retval 0 OK
* @retval -1 Error
* @see xml_addsub where xc is appended. xml_insert is xml_addsub();xml_sort()
* @note It is assumed that all siblings of xi are YANG bound
*/
int
xml_insert(cxobj *xp,

View file

@ -2,7 +2,9 @@
# Authentication and authorization and IETF NACM
# See RFC 8341 A.2
# But replaced ietf-netconf-monitoring with *
# Note credenials check set to none since USER poses as different users.
# Note:
# 1. credenials check set to none since USER poses as different users.
# 2. CLICON_NACM_DISABLE_ON_EMPTY: start with empty config and add nacm config
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
@ -31,6 +33,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config>
EOF
@ -140,7 +143,7 @@ expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/da
# explicitly disable nacm (regression on netgate bug)
new "disable nacm"
expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 201 Created"
expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": false}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content"
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>]]>]]>$"

View file

@ -123,6 +123,7 @@ cat <<EOF > $cfg
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
<CLICON_NACM_CREDENTIALS>$mode</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

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

View file

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

View file

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

View file

@ -30,6 +30,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
</clixon-config>
EOF
@ -62,7 +63,6 @@ EOF
# 6: expected return value of test2
# 7: expected return value of test3
# 8: startup mode: startup or init
# 9: Dont set default values (nullify them)
testrun(){
enablenacm=$1
readdefault=$2
@ -72,22 +72,7 @@ testrun(){
ret2=$6
ret3=$7
db=$8
nulldef=$9
# Set default values (or not)
if [ $nulldef -ne 0 ]; then
# Defaults should be: true permit deny permit:
# nacm enabled, exec default permit, read permit (expect fail)"
# which means results should be 0 1 3
# Also enable-nacm is present since otherwise the nacm container would be removed
# since it is non-presence
NACM=$(cat <<EOF
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
<enable-nacm>${enablenacm}</enable-nacm>
</nacm>
EOF
)
else
NACM=$(cat <<EOF
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
<enable-nacm>${enablenacm}</enable-nacm>
@ -98,7 +83,6 @@ EOF
</nacm>
EOF
)
fi
# Initial data
XML='<x xmlns="urn:example:nacm">42</x>'
@ -117,6 +101,7 @@ EOF
start_backend -s $db -f $cfg
else
new "Restart backend as eg follows: -Ff $cfg -s $db"
sleep 2
fi
new "waiting"
@ -132,12 +117,16 @@ EOF
wait_restconf
# Use POST (instead of startup)
# Note this only works because CLICON_NACM_DISABLED_ON_EMPTY is true
if [ $db = init ]; then
new "Set Initial data using POST"
expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
# Must set NACM first
new "Set NACM using PATCH"
expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+xml" -d "<data>$NACM$XML</data>" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 204 No Content"
# new "Set Initial data using POST"
# expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$XML" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
new "Set NACM using POST"
expectpart "$(curl -u guest:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+xml" -d "$NACM" $RCPROTO://localhost/restconf/data)" 0 "HTTP/1.1 201 Created"
fi
#----------- First get
@ -204,39 +193,36 @@ EOF
# Run a lot of tests with different settings of default read/write/exec
# Outer loop either starts from startup or inits config via restconf POST
for db in startup init; do
new "nacm enabled and all defaults permit"
testrun true permit permit permit 0 0 0 $db 0
new "1. nacm enabled and all defaults permit"
testrun true permit permit permit 0 0 0 $db
new "nacm disabled and all defaults permit"
testrun false permit permit permit 0 0 0 $db 0
new "2. nacm disabled and all defaults permit"
testrun false permit permit permit 0 0 0 $db
new "nacm disabled and all defaults deny"
testrun false deny deny deny 0 0 0 $db 0
new "3. nacm disabled and all defaults deny"
testrun false deny deny deny 0 0 0 $db
new "nacm enabled, all defaults deny (expect fail)"
testrun true deny deny deny 1 1 1 $db 0
new "4. nacm enabled, all defaults deny (expect fail)"
testrun true deny deny deny 1 1 1 $db
new "nacm enabled, exec default deny - read permit (expect fail)"
testrun true permit deny deny 1 1 1 $db 0
new "5. nacm enabled, exec default deny - read permit (expect fail)"
testrun true permit deny deny 1 1 1 $db
new "nacm enabled, exec default deny - write permit (expect fail)"
testrun true deny permit deny 1 1 1 $db 0
new "6. nacm enabled, exec default deny - write permit (expect fail)"
testrun true deny permit deny 1 1 1 $db
new "nacm enabled, exec default deny read/write permit (expect fail)"
testrun true permit permit deny 1 1 1 $db 0
new "7. nacm enabled, exec default deny read/write permit (expect fail)"
testrun true permit permit deny 1 1 1 $db
new "nacm enabled, exec default permit, all others deny (expect fail)"
testrun true deny deny permit 2 1 2 $db 0
new "8. nacm enabled, exec default permit, all others deny (expect fail)"
testrun true deny deny permit 2 1 2 $db
new "nacm enabled, exec default permit, read permit (expect fail)"
testrun true permit deny permit 0 1 3 $db 0 # This is yang default
new "9. nacm enabled, exec default permit, read permit (expect fail)"
testrun true permit deny permit 0 1 3 $db # This is yang default
new "nacm enabled, with default values (no settings - should be same as previous)"
# note last 1 means nullify all default values)
testrun true xxx xxx xxx 0 1 3 init 1
new "10. nacm enabled, exec default permit, write permit (expect fail)"
testrun true deny permit permit 2 0 2 $db
new "nacm enabled, exec default permit, write permit (expect fail)"
testrun true deny permit permit 2 0 2 $db 0
done
rm -rf $dir

View file

@ -48,6 +48,7 @@ module nacm-example{
}
prefix nacm;
container authentication {
presence "To keep this from auto-expanding";
description "Example code for enabling www basic auth and some example
users";
leaf basic_auth{

View file

@ -35,6 +35,7 @@ cat <<EOF > $cfg
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config>
EOF

View file

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

View file

@ -52,6 +52,7 @@ cat <<EOF > $cfg
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config>
EOF

View file

@ -27,9 +27,6 @@ module scaling{
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
import "clixon-config" {
prefix cc;
}
container x {
list y {
key "a";

View file

@ -26,6 +26,7 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_DISABLED_ON_EMPTY>true</CLICON_NACM_DISABLED_ON_EMPTY>
</clixon-config>
EOF

View file

@ -40,7 +40,6 @@ cat <<EOF > $cfg
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
</clixon-config>
EOF
# Create running-db containin the interface "run" OK

View file

@ -40,6 +40,11 @@ module interfaces{
reference
"RFC 2863: The Interfaces Group MIB";
}
leaf foo{
description "Should not appear";
type string;
default "bar";
}
container interfaces {
description
"Interface configuration parameters.";
@ -52,8 +57,11 @@ module interfaces{
leaf description {
type string;
}
leaf foo{
description "Should not appear";
type string;
default "bar";
}
leaf type {
type string;
mandatory true;
@ -124,6 +132,11 @@ module interfaces{
reference
"RFC 2863: The Interfaces Group MIB";
}
leaf foo{
description "Should not appear";
type string;
default "fie";
}
container interfaces {
description
"Interface configuration parameters.";
@ -133,6 +146,11 @@ module interfaces{
leaf name {
type string;
}
leaf foo{
description "Should not appear";
type string;
default "bar";
}
container docs{
description "Original description is wrapped and renamed";
leaf descr {
@ -290,7 +308,6 @@ XML='<interfaces xmlns="urn:example:interfaces"><interface><name>e0</name><type>
ALL="<config>$MODSTATE$XML</config>"
# -u means trigger example upgrade
new "test params: -s startup -f $cfg -- -u"
# kill old backend (if any)
new "kill old backend"
@ -301,6 +318,7 @@ fi
new "start backend -s startup -f $cfg -q -- -u"
output=$(sudo $clixon_backend -F -D $DBG -s startup -f $cfg -q -- -u)
#echo "$output"
if [ "$ALL" != "$output" ]; then
err "$ALL" "$output"
fi

View file

@ -46,9 +46,8 @@ module clixon-config {
description
"Added: CLICON_CLI_LINES_DEFAULT
Added enum HIDE to CLICON_CLI_GENMODEL
Added CLICON_SSL_SERVER_CERT
Added CLICON_SSL_SERVER_KEY
Added CLICON_SSL_CA_CERT";
Added CLICON_SSL_SERVER_CERT, CLICON_SSL_SERVER_KEY, CLICON_SSL_CA_CERT
Added CLICON_NACM_DISABLED_ON_EMPTY";
}
revision 2020-04-23 {
description
@ -719,6 +718,20 @@ module clixon-config {
exact for example, this user must exist and be used, otherwise
another user (such as root or www) can pose as it.";
}
leaf CLICON_NACM_DISABLED_ON_EMPTY {
type boolean;
default false;
description
"RFC 8341 and ietf-netconf-acm@2018-02-14.yang defines enable-nacm as true by
default. Since also write-default is deny by default it leads to that empty
configs can not be edited.
This means that a startup config must always have a NACM configuration or
that the NACM recovery session is used to edit an empty config.
If this option is set, Clixon disables NACM if a datastore is empty on load.
Note that it only makes the check on initial load, not if a store 'becomes'
empty, but enables a clixon nacm system to start empty and add an NACM
config after boot.";
}
leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean;
default true;