Merge branch 'nacm' into develop
This commit is contained in:
commit
cf4f626719
32 changed files with 1733 additions and 328 deletions
15
CHANGELOG.md
15
CHANGELOG.md
|
|
@ -76,13 +76,19 @@
|
||||||
* Note CLIXON_DATADIR (=/usr/local/share/clixon) need to be in the list
|
* 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_FILE Provides a filename with a single module filename.
|
||||||
* CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded.
|
* CLICON_YANG_MAIN_DIR Provides a directory where all yang modules should be loaded.
|
||||||
* NACM extension (RFC8341)
|
* NACM (RFC8341) experimental
|
||||||
* NACM module support (RFC8341 A1+A2)
|
* 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.
|
* 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)
|
### 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
|
* Rearranged yang files
|
||||||
* Moved and updated all standard ietf and iana yang files from example and yang/ to `yang/standard`.
|
* 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`
|
* 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
|
* For backward compatibility, define CLICON_CLI_MODEL_TREENAME_PATCH in clixon_custom.h
|
||||||
|
|
||||||
### Minor changes
|
### 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.
|
* 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.
|
* 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.
|
* Added -p <dir> command-line option to all programs: backend, cli, netconf, restconf.
|
||||||
|
|
|
||||||
39
README.md
39
README.md
|
|
@ -220,35 +220,28 @@ so the clients can in principle fake a username.
|
||||||
|
|
||||||
NACM
|
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)
|
* The `CLICON_NACM_MODE` config variable is by default `disabled`.
|
||||||
* 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.
|
* 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.
|
* 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
|
The [example](example/README.md) contains a http basic auth and a NACM backend callback for mandatory state variables.
|
||||||
in `from_client_msg()` when an internal netconf RPC has
|
|
||||||
just been received and decoded. The code is in `nacm_access()`.
|
|
||||||
|
|
||||||
The functionality is as follows:
|
NACM is implemented in the backend with incoming RPC and data node access control points.
|
||||||
* 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)
|
|
||||||
|
|
||||||
The tests outlines an example of three groups (taken from the RFC): admin, limited and guest:
|
The functionality is as follows (references to sections in [RFC8341](https://tools.ietf.org/html/rfc8341)):
|
||||||
* admin: Full access
|
* Access control point support:
|
||||||
* limited: Read access (get and get-config)
|
* Incoming RPC Message validation is supported (3.4.4)
|
||||||
* guest: No access
|
* 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
|
Runtime
|
||||||
=======
|
=======
|
||||||
|
|
|
||||||
|
|
@ -177,14 +177,19 @@ netconf_db_find(cxobj *xn,
|
||||||
static int
|
static int
|
||||||
from_client_get_config(clicon_handle h,
|
from_client_get_config(clicon_handle h,
|
||||||
cxobj *xe,
|
cxobj *xe,
|
||||||
|
char *username,
|
||||||
cbuf *cbret)
|
cbuf *cbret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *db;
|
char *db;
|
||||||
cxobj *xfilter;
|
cxobj *xfilter;
|
||||||
char *selector = "/";
|
char *xpath = "/";
|
||||||
cxobj *xret = NULL;
|
cxobj *xret = NULL;
|
||||||
cbuf *cbx = NULL; /* Assist cbuf */
|
cbuf *cbx = NULL; /* Assist cbuf */
|
||||||
|
cxobj *xnacm = NULL;
|
||||||
|
cxobj **xvec = NULL;
|
||||||
|
size_t xlen;
|
||||||
|
int ret;
|
||||||
|
|
||||||
if ((db = netconf_db_find(xe, "source")) == NULL){
|
if ((db = netconf_db_find(xe, "source")) == NULL){
|
||||||
clicon_err(OE_XML, 0, "db not found");
|
clicon_err(OE_XML, 0, "db not found");
|
||||||
|
|
@ -201,13 +206,23 @@ from_client_get_config(clicon_handle h,
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
if ((xfilter = xml_find(xe, "filter")) != NULL)
|
if ((xfilter = xml_find(xe, "filter")) != NULL)
|
||||||
if ((selector = xml_find_value(xfilter, "select"))==NULL)
|
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
||||||
selector="/";
|
xpath="/";
|
||||||
if (xmldb_get(h, db, selector, 1, &xret) < 0){
|
if (xmldb_get(h, db, xpath, 1, &xret) < 0){
|
||||||
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
if (netconf_operation_failed(cbret, "application", "read registry")< 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
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>");
|
cprintf(cbret, "<rpc-reply>");
|
||||||
if (xret==NULL)
|
if (xret==NULL)
|
||||||
cprintf(cbret, "<data/>");
|
cprintf(cbret, "<data/>");
|
||||||
|
|
@ -221,6 +236,10 @@ from_client_get_config(clicon_handle h,
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
if (xnacm)
|
||||||
|
xml_free(xnacm);
|
||||||
|
if (xvec)
|
||||||
|
free(xvec);
|
||||||
if (cbx)
|
if (cbx)
|
||||||
cbuf_free(cbx);
|
cbuf_free(cbx);
|
||||||
if (xret)
|
if (xret)
|
||||||
|
|
@ -356,6 +375,7 @@ client_statedata(clicon_handle h,
|
||||||
static int
|
static int
|
||||||
from_client_get(clicon_handle h,
|
from_client_get(clicon_handle h,
|
||||||
cxobj *xe,
|
cxobj *xe,
|
||||||
|
char *username,
|
||||||
cbuf *cbret)
|
cbuf *cbret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -363,7 +383,10 @@ from_client_get(clicon_handle h,
|
||||||
char *xpath = "/";
|
char *xpath = "/";
|
||||||
cxobj *xret = NULL;
|
cxobj *xret = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
cxobj **xvec = NULL;
|
||||||
|
size_t xlen;
|
||||||
|
cxobj *xnacm = NULL;
|
||||||
|
|
||||||
if ((xfilter = xml_find(xe, "filter")) != NULL)
|
if ((xfilter = xml_find(xe, "filter")) != NULL)
|
||||||
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
if ((xpath = xml_find_value(xfilter, "select"))==NULL)
|
||||||
xpath="/";
|
xpath="/";
|
||||||
|
|
@ -383,6 +406,16 @@ from_client_get(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
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 */
|
cprintf(cbret, "<rpc-reply>"); /* OK */
|
||||||
if (xret==NULL)
|
if (xret==NULL)
|
||||||
cprintf(cbret, "<data/>");
|
cprintf(cbret, "<data/>");
|
||||||
|
|
@ -396,6 +429,10 @@ from_client_get(clicon_handle h,
|
||||||
ok:
|
ok:
|
||||||
retval = 0;
|
retval = 0;
|
||||||
done:
|
done:
|
||||||
|
if (xnacm)
|
||||||
|
xml_free(xnacm);
|
||||||
|
if (xvec)
|
||||||
|
free(xvec);
|
||||||
if (xret)
|
if (xret)
|
||||||
xml_free(xret);
|
xml_free(xret);
|
||||||
return retval;
|
return retval;
|
||||||
|
|
@ -412,6 +449,7 @@ static int
|
||||||
from_client_edit_config(clicon_handle h,
|
from_client_edit_config(clicon_handle h,
|
||||||
cxobj *xn,
|
cxobj *xn,
|
||||||
int mypid,
|
int mypid,
|
||||||
|
char *username,
|
||||||
cbuf *cbret)
|
cbuf *cbret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -485,7 +523,7 @@ from_client_edit_config(clicon_handle h,
|
||||||
*/
|
*/
|
||||||
if (xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0)
|
if (xml_apply0(xc, CX_ELMNT, xml_sort, NULL) < 0)
|
||||||
goto done;
|
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__);
|
clicon_debug(1, "%s ERROR PUT", __FUNCTION__);
|
||||||
if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0)
|
if (netconf_operation_failed(cbret, "protocol", clicon_err_reason)< 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -690,6 +728,10 @@ from_client_kill_session(clicon_handle h,
|
||||||
* @param[out] cbret Return xml value cligen buffer
|
* @param[out] cbret Return xml value cligen buffer
|
||||||
* @retval 0 OK
|
* @retval 0 OK
|
||||||
* @retval -1 Error. Send error message back to client.
|
* @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
|
static int
|
||||||
from_client_copy_config(clicon_handle h,
|
from_client_copy_config(clicon_handle h,
|
||||||
|
|
@ -963,7 +1005,8 @@ from_client_msg(clicon_handle h,
|
||||||
yang_spec *yspec;
|
yang_spec *yspec;
|
||||||
yang_stmt *ye;
|
yang_stmt *ye;
|
||||||
yang_stmt *ymod;
|
yang_stmt *ymod;
|
||||||
|
cxobj *xnacm = NULL;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
pid = ce->ce_pid;
|
pid = ce->ce_pid;
|
||||||
yspec = clicon_dbspec_yang(h);
|
yspec = clicon_dbspec_yang(h);
|
||||||
|
|
@ -996,6 +1039,8 @@ from_client_msg(clicon_handle h,
|
||||||
goto reply;
|
goto reply;
|
||||||
xe = NULL;
|
xe = NULL;
|
||||||
username = xml_find_value(x, "username");
|
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) {
|
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
|
||||||
rpc = xml_name(xe);
|
rpc = xml_name(xe);
|
||||||
if ((ye = xml_spec(xe)) == NULL){
|
if ((ye = xml_spec(xe)) == NULL){
|
||||||
|
|
@ -1009,17 +1054,25 @@ from_client_msg(clicon_handle h,
|
||||||
}
|
}
|
||||||
module = ymod->ys_argument;
|
module = ymod->ys_argument;
|
||||||
clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc);
|
clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc);
|
||||||
/* Make NACM access control if enabled as "internal"*/
|
/* Pre-NACM access step */
|
||||||
if ((ret = nacm_access(h, rpc, module, username, cbret)) < 0)
|
xnacm = NULL;
|
||||||
|
if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if (ret == 0)
|
if (ret == 0){ /* Do NACM validation */
|
||||||
goto reply;
|
/* 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 (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;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (strcmp(rpc, "edit-config") == 0){
|
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;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (strcmp(rpc, "copy-config") == 0){
|
else if (strcmp(rpc, "copy-config") == 0){
|
||||||
|
|
@ -1039,7 +1092,7 @@ from_client_msg(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (strcmp(rpc, "get") == 0){
|
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;
|
goto done;
|
||||||
}
|
}
|
||||||
else if (strcmp(rpc, "close-session") == 0){
|
else if (strcmp(rpc, "close-session") == 0){
|
||||||
|
|
|
||||||
|
|
@ -285,7 +285,8 @@ candidate_commit(clicon_handle h,
|
||||||
|
|
||||||
/* Optionally write (potentially modified) tree back to candidate */
|
/* Optionally write (potentially modified) tree back to candidate */
|
||||||
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
|
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 done;
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
@ -322,6 +323,10 @@ candidate_commit(clicon_handle h,
|
||||||
* @param[out] cbret Return xml value cligen buffer
|
* @param[out] cbret Return xml value cligen buffer
|
||||||
* @retval 0 OK. This may indicate both ok and err msg back to client
|
* @retval 0 OK. This may indicate both ok and err msg back to client
|
||||||
* @retval -1 (Local) Error
|
* @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
|
int
|
||||||
from_client_commit(clicon_handle h,
|
from_client_commit(clicon_handle h,
|
||||||
|
|
@ -367,6 +372,7 @@ from_client_commit(clicon_handle h,
|
||||||
* @param[out] cbret Return xml value cligen buffer
|
* @param[out] cbret Return xml value cligen buffer
|
||||||
* @retval 0 OK. This may indicate both ok and err msg back to client
|
* @retval 0 OK. This may indicate both ok and err msg back to client
|
||||||
* @retval -1 (Local) Error
|
* @retval -1 (Local) Error
|
||||||
|
* NACM: No datastore permissions are needed.
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
from_client_discard_changes(clicon_handle h,
|
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 */
|
/* Optionally write (potentially modified) tree back to candidate */
|
||||||
if (clicon_option_bool(h, "CLICON_TRANSACTION_MOD")){
|
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 done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ db_merge(clicon_handle h,
|
||||||
if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0)
|
if (xmldb_get(h, (char*)db1, NULL, 1, &xt) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Merge xml into db2. Without commit */
|
/* 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:
|
done:
|
||||||
if (xt)
|
if (xt)
|
||||||
xml_free(xt);
|
xml_free(xt);
|
||||||
|
|
@ -318,7 +318,7 @@ load_extraxml(clicon_handle h,
|
||||||
if (xml_rootchild(xt, 0, &xt) < 0)
|
if (xml_rootchild(xt, 0, &xt) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Merge user reset state */
|
/* 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:
|
done:
|
||||||
if (fd != -1)
|
if (fd != -1)
|
||||||
close(fd);
|
close(fd);
|
||||||
|
|
@ -857,6 +857,10 @@ main(int argc,
|
||||||
goto done;
|
goto done;
|
||||||
if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0)
|
if (xmldb_setopt(h, "pretty", (void*)(intptr_t)clicon_option_bool(h, "CLICON_XMLDB_PRETTY")) < 0)
|
||||||
goto done;
|
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 needs to be defined, */
|
||||||
startup_mode = clicon_startup_mode(h);
|
startup_mode = clicon_startup_mode(h);
|
||||||
if (startup_mode == -1){
|
if (startup_mode == -1){
|
||||||
|
|
@ -906,7 +910,6 @@ main(int argc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Write pid-file */
|
/* Write pid-file */
|
||||||
|
|
||||||
if ((pid = pidfile_write(pidfile)) < 0)
|
if ((pid = pidfile_write(pidfile)) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -212,6 +212,7 @@ yang2cli_var_sub(clicon_handle h,
|
||||||
if (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0){
|
if (strcmp(type, "enumeration") == 0 || strcmp(type, "bits") == 0){
|
||||||
cprintf(cb, " choice:");
|
cprintf(cb, " choice:");
|
||||||
i = 0;
|
i = 0;
|
||||||
|
yi = NULL;
|
||||||
while ((yi = yn_each((yang_node*)ytype, yi)) != NULL){
|
while ((yi = yn_each((yang_node*)ytype, yi)) != NULL){
|
||||||
if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT)
|
if (yi->ys_keyword != Y_ENUM && yi->ys_keyword != Y_BIT)
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -779,7 +779,7 @@ api_data_put(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* The message-body MUST contain exactly one instance of the
|
/* The message-body MUST contain exactly one instance of the
|
||||||
* expected data resource.
|
* expected data resource.
|
||||||
*/
|
*/
|
||||||
|
|
@ -808,6 +808,7 @@ api_data_put(clicon_handle h,
|
||||||
goto ok;
|
goto ok;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add operation (create/replace) as attribute */
|
/* Add operation (create/replace) as attribute */
|
||||||
if ((xa = xml_new("operation", x, NULL)) == NULL)
|
if ((xa = xml_new("operation", x, NULL)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -832,6 +833,7 @@ api_data_put(clicon_handle h,
|
||||||
xml_name_set(xtop, "config");
|
xml_name_set(xtop, "config");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
clicon_debug(1, "%s x:%s xbot:%s",__FUNCTION__, xml_name(x), xml_name(xbot));
|
||||||
/* Check same symbol in api-path as data */
|
/* Check same symbol in api-path as data */
|
||||||
if (strcmp(xml_name(x), xml_name(xbot))){
|
if (strcmp(xml_name(x), xml_name(xbot))){
|
||||||
if (netconf_operation_failed_xml(&xerr, "protocol", "Not same symbol in api-path as data") < 0)
|
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 */
|
by NACM */
|
||||||
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
|
cprintf(cbx, "<rpc username=\"%s\">", NACM_RECOVERY_USER);
|
||||||
cprintf(cbx, "<commit/></rpc>");
|
cprintf(cbx, "<commit/></rpc>");
|
||||||
|
|
||||||
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
|
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){
|
if ((xe = xpath_first(xretcom, "//rpc-error")) != NULL){
|
||||||
|
|
|
||||||
|
|
@ -248,7 +248,7 @@ main(int argc, char **argv)
|
||||||
clicon_err(OE_UNIX, errno, "cbuf_new");
|
clicon_err(OE_UNIX, errno, "cbuf_new");
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
if (xmldb_put(h, db, op, xt, cbret) < 1)
|
if (xmldb_put(h, db, op, xt, NULL, cbret) < 1)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,8 @@ struct text_handle {
|
||||||
Assumes single backend*/
|
Assumes single backend*/
|
||||||
char *th_format; /* Datastroe format: xml / json */
|
char *th_format; /* Datastroe format: xml / json */
|
||||||
int th_pretty; /* Store xml/json pretty-printed. */
|
int th_pretty; /* Store xml/json pretty-printed. */
|
||||||
|
char *th_nacm_mode;
|
||||||
|
cxobj *th_nacm_xtree;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Struct per database in hash */
|
/* Struct per database in hash */
|
||||||
|
|
@ -207,6 +209,8 @@ text_disconnect(xmldb_handle xh)
|
||||||
}
|
}
|
||||||
hash_free(th->th_dbs);
|
hash_free(th->th_dbs);
|
||||||
}
|
}
|
||||||
|
if (th->th_nacm_mode)
|
||||||
|
free(th->th_nacm_mode);
|
||||||
free(th);
|
free(th);
|
||||||
}
|
}
|
||||||
retval = 0;
|
retval = 0;
|
||||||
|
|
@ -239,6 +243,10 @@ text_getopt(xmldb_handle xh,
|
||||||
*value = th->th_format;
|
*value = th->th_format;
|
||||||
else if (strcmp(optname, "pretty") == 0)
|
else if (strcmp(optname, "pretty") == 0)
|
||||||
*value = &th->th_pretty;
|
*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{
|
else{
|
||||||
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -288,6 +296,15 @@ text_setopt(xmldb_handle xh,
|
||||||
else if (strcmp(optname, "pretty") == 0){
|
else if (strcmp(optname, "pretty") == 0){
|
||||||
th->th_pretty = (intptr_t)value;
|
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{
|
else{
|
||||||
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
clicon_err(OE_PLUGIN, 0, "Option %s not implemented by plugin", optname);
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -421,10 +438,17 @@ xml_copy_marked(cxobj *x0,
|
||||||
* The function returns a minimal tree that includes all sub-trees that match
|
* The function returns a minimal tree that includes all sub-trees that match
|
||||||
* xpath.
|
* xpath.
|
||||||
* This is a clixon datastore plugin of the the xmldb api
|
* 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
|
int
|
||||||
text_get(xmldb_handle xh,
|
text_get(xmldb_handle xh,
|
||||||
const char *db,
|
const char *db,
|
||||||
char *xpath,
|
char *xpath,
|
||||||
int config,
|
int config,
|
||||||
|
|
@ -434,6 +458,7 @@ text_get(xmldb_handle xh,
|
||||||
char *dbfile = NULL;
|
char *dbfile = NULL;
|
||||||
yang_spec *yspec;
|
yang_spec *yspec;
|
||||||
cxobj *xt = NULL;
|
cxobj *xt = NULL;
|
||||||
|
cxobj *x;
|
||||||
int fd = -1;
|
int fd = -1;
|
||||||
cxobj **xvec = NULL;
|
cxobj **xvec = NULL;
|
||||||
size_t xlen;
|
size_t xlen;
|
||||||
|
|
@ -486,7 +511,6 @@ text_get(xmldb_handle xh,
|
||||||
*/
|
*/
|
||||||
} /* xt == NULL */
|
} /* xt == NULL */
|
||||||
/* Here xt looks like: <config>...</config> */
|
/* Here xt looks like: <config>...</config> */
|
||||||
|
|
||||||
if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
if (xpath_vec(xt, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
|
|
@ -496,11 +520,13 @@ text_get(xmldb_handle xh,
|
||||||
*/
|
*/
|
||||||
if (xvec != NULL)
|
if (xvec != NULL)
|
||||||
for (i=0; i<xlen; i++){
|
for (i=0; i<xlen; i++){
|
||||||
xml_flag_set(xvec[i], XML_FLAG_MARK);
|
x = xvec[i];
|
||||||
if (th->th_cache)
|
xml_flag_set(x, XML_FLAG_MARK);
|
||||||
xml_apply_ancestor(xvec[i], (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
|
if (1 || th->th_cache)
|
||||||
|
xml_apply_ancestor(x, (xml_applyfn_t*)xml_flag_set, (void*)XML_FLAG_CHANGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (th->th_cache){
|
if (th->th_cache){
|
||||||
/* Copy the matching parts of the (relevant) XML tree.
|
/* Copy the matching parts of the (relevant) XML tree.
|
||||||
* If cache was NULL, also write to datastore cache
|
* If cache was NULL, also write to datastore cache
|
||||||
|
|
@ -534,13 +560,16 @@ text_get(xmldb_handle xh,
|
||||||
/* reset flag */
|
/* reset flag */
|
||||||
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
|
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
/* filter out state (operations) data if config not set. Mark all nodes
|
/* filter out state (operations) data if config not set. Mark all nodes
|
||||||
that are not config data */
|
that are not config data */
|
||||||
if (config && xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
|
if (config){
|
||||||
goto done;
|
if (xml_apply(xt, CX_ELMNT, xml_non_config_data, NULL) < 0)
|
||||||
/* Remove (prune) nodes that are marked (that does not pass test) */
|
goto done;
|
||||||
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
|
/* Remove (prune) nodes that are marked (that does not pass test) */
|
||||||
goto done;
|
if (xml_tree_prune_flagged(xt, XML_FLAG_MARK, 1) < 0)
|
||||||
|
goto done;
|
||||||
|
}
|
||||||
/* Add default values (if not set) */
|
/* Add default values (if not set) */
|
||||||
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
|
if (xml_apply(xt, CX_ELMNT, xml_default, NULL) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -572,6 +601,9 @@ text_get(xmldb_handle xh,
|
||||||
* @param[in] x0p Parent of x0
|
* @param[in] x0p Parent of x0
|
||||||
* @param[in] x1 xml tree which modifies base
|
* @param[in] x1 xml tree which modifies base
|
||||||
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
|
* @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.
|
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @retval 0 Failed (cbret set)
|
* @retval 0 Failed (cbret set)
|
||||||
|
|
@ -586,6 +618,9 @@ text_modify(struct text_handle *th,
|
||||||
cxobj *x0p,
|
cxobj *x0p,
|
||||||
cxobj *x1,
|
cxobj *x1,
|
||||||
enum operation_type op,
|
enum operation_type op,
|
||||||
|
char *username,
|
||||||
|
cxobj *xnacm,
|
||||||
|
int permit,
|
||||||
cbuf *cbret)
|
cbuf *cbret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -597,6 +632,7 @@ text_modify(struct text_handle *th,
|
||||||
cxobj *x0c; /* base child */
|
cxobj *x0c; /* base child */
|
||||||
cxobj *x0b; /* base body */
|
cxobj *x0b; /* base body */
|
||||||
cxobj *x1c; /* mod child */
|
cxobj *x1c; /* mod child */
|
||||||
|
char *x0bstr; /* mod body string */
|
||||||
char *x1bstr; /* mod body string */
|
char *x1bstr; /* mod body string */
|
||||||
yang_stmt *yc; /* yang child */
|
yang_stmt *yc; /* yang child */
|
||||||
cxobj **x0vec = NULL;
|
cxobj **x0vec = NULL;
|
||||||
|
|
@ -623,6 +659,13 @@ text_modify(struct text_handle *th,
|
||||||
case OP_MERGE:
|
case OP_MERGE:
|
||||||
case OP_REPLACE:
|
case OP_REPLACE:
|
||||||
if (x0==NULL){
|
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;
|
// int iamkey=0;
|
||||||
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
|
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
@ -649,13 +692,21 @@ text_modify(struct text_handle *th,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (x1bstr){
|
if (x1bstr){
|
||||||
if ((x0b = xml_body_get(x0)) == NULL){
|
if ((x0b = xml_body_get(x0)) != NULL){
|
||||||
if ((x0b = xml_new("body", x0, NULL)) == NULL)
|
x0bstr = xml_value(x0b);
|
||||||
goto done;
|
if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){
|
||||||
xml_type_set(x0b, CX_BODY);
|
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;
|
break;
|
||||||
case OP_DELETE:
|
case OP_DELETE:
|
||||||
|
|
@ -666,7 +717,14 @@ text_modify(struct text_handle *th,
|
||||||
}
|
}
|
||||||
case OP_REMOVE: /* fall thru */
|
case OP_REMOVE: /* fall thru */
|
||||||
if (x0){
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -682,6 +740,13 @@ text_modify(struct text_handle *th,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
case OP_REPLACE: /* fall thru */
|
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){
|
if (x0){
|
||||||
xml_purge(x0);
|
xml_purge(x0);
|
||||||
x0 = NULL;
|
x0 = NULL;
|
||||||
|
|
@ -689,10 +754,21 @@ text_modify(struct text_handle *th,
|
||||||
case OP_MERGE: /* fall thru */
|
case OP_MERGE: /* fall thru */
|
||||||
case OP_NONE:
|
case OP_NONE:
|
||||||
/* Special case: anyxml, just replace tree,
|
/* 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 (y0->yn_keyword == Y_ANYXML){
|
||||||
if (op == OP_NONE)
|
if (op == OP_NONE)
|
||||||
break;
|
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){
|
if (x0){
|
||||||
xml_purge(x0);
|
xml_purge(x0);
|
||||||
}
|
}
|
||||||
|
|
@ -703,6 +779,13 @@ text_modify(struct text_handle *th,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (x0==NULL){
|
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)
|
if ((x0 = xml_new(x1name, x0p, (yang_stmt*)y0)) == NULL)
|
||||||
goto done;
|
goto done;
|
||||||
/* Copy xmlns attributes */
|
/* Copy xmlns attributes */
|
||||||
|
|
@ -755,7 +838,8 @@ text_modify(struct text_handle *th,
|
||||||
x1cname = xml_name(x1c);
|
x1cname = xml_name(x1c);
|
||||||
x0c = x0vec[i++];
|
x0c = x0vec[i++];
|
||||||
yc = yang_find_datanode(y0, x1cname);
|
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;
|
goto done;
|
||||||
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
|
|
@ -769,8 +853,16 @@ text_modify(struct text_handle *th,
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
case OP_REMOVE: /* fall thru */
|
case OP_REMOVE: /* fall thru */
|
||||||
if (x0)
|
if (x0){
|
||||||
xml_purge(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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|
@ -793,6 +885,8 @@ text_modify(struct text_handle *th,
|
||||||
* @param[in] x1 xml tree which modifies base
|
* @param[in] x1 xml tree which modifies base
|
||||||
* @param[in] yspec Top-level yang spec (if y is NULL)
|
* @param[in] yspec Top-level yang spec (if y is NULL)
|
||||||
* @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc
|
* @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.
|
* @param[out] cbret Initialized cligen buffer. Contains return XML if retval is 0.
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @retval 0 Failed (cbret set)
|
* @retval 0 Failed (cbret set)
|
||||||
|
|
@ -805,6 +899,8 @@ text_modify_top(struct text_handle *th,
|
||||||
cxobj *x1,
|
cxobj *x1,
|
||||||
yang_spec *yspec,
|
yang_spec *yspec,
|
||||||
enum operation_type op,
|
enum operation_type op,
|
||||||
|
char *username,
|
||||||
|
cxobj *xnacm,
|
||||||
cbuf *cbret)
|
cbuf *cbret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -815,6 +911,7 @@ text_modify_top(struct text_handle *th,
|
||||||
yang_stmt *ymod;/* yang module */
|
yang_stmt *ymod;/* yang module */
|
||||||
char *opstr;
|
char *opstr;
|
||||||
int ret;
|
int ret;
|
||||||
|
int permit = 0;
|
||||||
|
|
||||||
/* Assure top-levels are 'config' */
|
/* Assure top-levels are 'config' */
|
||||||
assert(x0 && strcmp(xml_name(x0),"config")==0);
|
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)
|
if (xml_operation(opstr, &op) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
/* Special case if x1 is empty, top-level only <config/> */
|
/* Special case if x1 is empty, top-level only <config/> */
|
||||||
if (xml_child_nr(x1) == 0){
|
if (xml_child_nr_type(x1, CX_ELMNT) == 0){
|
||||||
if (xml_child_nr(x0)) /* base tree not empty */
|
if (xml_child_nr_type(x0, CX_ELMNT)){ /* base tree not empty */
|
||||||
switch(op){
|
switch(op){
|
||||||
case OP_DELETE:
|
case OP_DELETE:
|
||||||
case OP_REMOVE:
|
case OP_REMOVE:
|
||||||
case OP_REPLACE:
|
case OP_REPLACE:
|
||||||
x0c = NULL;
|
if (xnacm){
|
||||||
while ((x0c = xml_child_each(x0, x0c, CX_ELMNT)) != NULL)
|
if ((ret = nacm_datanode_write(NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0)
|
||||||
xml_purge(x0c);
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else /* base tree empty */
|
else /* base tree empty */
|
||||||
switch(op){
|
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:
|
case OP_DELETE:
|
||||||
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
|
if (netconf_data_missing(cbret, "Data does not exist; cannot delete resource") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto fail;
|
goto fail;
|
||||||
break;
|
break;
|
||||||
|
#endif
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Special case top-level replace */
|
/* Special case top-level replace */
|
||||||
if (op == OP_REPLACE || op == OP_DELETE){
|
else if (op == OP_REPLACE || op == OP_DELETE){
|
||||||
x0c = NULL;
|
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)
|
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 */
|
/* Loop through children of the modification tree */
|
||||||
x1c = NULL;
|
x1c = NULL;
|
||||||
|
|
@ -885,7 +1003,8 @@ text_modify_top(struct text_handle *th,
|
||||||
x0c = NULL;
|
x0c = NULL;
|
||||||
}
|
}
|
||||||
#endif
|
#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;
|
goto done;
|
||||||
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
|
|
@ -939,13 +1058,23 @@ xml_container_presence(cxobj *x,
|
||||||
|
|
||||||
/*! Modify database provided an xml tree and an operation
|
/*! Modify database provided an xml tree and an operation
|
||||||
* This is a clixon datastore plugin of the the xmldb api
|
* 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
|
int
|
||||||
text_put(xmldb_handle xh,
|
text_put(xmldb_handle xh,
|
||||||
const char *db,
|
const char *db,
|
||||||
enum operation_type op,
|
enum operation_type op,
|
||||||
cxobj *x1,
|
cxobj *x1,
|
||||||
|
char *username,
|
||||||
cbuf *cbret)
|
cbuf *cbret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -958,6 +1087,7 @@ text_put(xmldb_handle xh,
|
||||||
cxobj *x0 = NULL;
|
cxobj *x0 = NULL;
|
||||||
struct db_element *de = NULL;
|
struct db_element *de = NULL;
|
||||||
int ret;
|
int ret;
|
||||||
|
cxobj *xnacm = NULL;
|
||||||
|
|
||||||
if (cbret == NULL){
|
if (cbret == NULL){
|
||||||
clicon_err(OE_XML, EINVAL, "cbret is NULL");
|
clicon_err(OE_XML, EINVAL, "cbret is NULL");
|
||||||
|
|
@ -1017,12 +1147,34 @@ text_put(xmldb_handle xh,
|
||||||
#if 0 /* debug */
|
#if 0 /* debug */
|
||||||
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
|
if (xml_apply0(x1, -1, xml_sort_verify, NULL) < 0)
|
||||||
clicon_log(LOG_NOTICE, "%s: verify failed #1", __FUNCTION__);
|
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
|
#endif
|
||||||
/*
|
/*
|
||||||
* Modify base tree x with modification x1. This is where the
|
* Modify base tree x with modification x1. This is where the
|
||||||
* new tree is made.
|
* 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;
|
goto done;
|
||||||
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
/* If xml return - ie netconf error xml tree, then stop and return OK */
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
* Prototypes
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
int text_get(xmldb_handle h, const char *db, char *xpath, int config, cxobj **xtop);
|
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_dump(FILE *f, char *dbfilename, char *rxkey);
|
||||||
int text_copy(xmldb_handle h, const char *from, const char *to);
|
int text_copy(xmldb_handle h, const char *from, const char *to);
|
||||||
int text_lock(xmldb_handle h, const char *db, int pid);
|
int text_lock(xmldb_handle h, const char *db, int pid);
|
||||||
|
|
|
||||||
|
|
@ -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
|
Yes. Systemd example files are provide for the backend and the
|
||||||
restconf daemon as part of the [example](../example/systemd).
|
restconf daemon as part of the [example](../example/systemd).
|
||||||
|
|
||||||
|
|
||||||
## How can I add extra XML?
|
## 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.
|
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
|
See [../apps/example/example_restconf.c] example_restconf_credentials() for
|
||||||
an example of HTTP basic auth.
|
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?
|
## How do I write a CLI translator function?
|
||||||
|
|
||||||
The CLI can perform variable translation. This is useful if you want to
|
The CLI can perform variable translation. This is useful if you want to
|
||||||
|
|
|
||||||
|
|
@ -243,7 +243,7 @@ example_reset(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
/* Merge user reset state */
|
/* 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;
|
goto done;
|
||||||
if (ret == 0){
|
if (ret == 0){
|
||||||
clicon_err(OE_XML, 0, "Error when writing to XML database: %s",
|
clicon_err(OE_XML, 0, "Error when writing to XML database: %s",
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ nacm_statedata(clicon_handle h,
|
||||||
cxobj **xvec = NULL;
|
cxobj **xvec = NULL;
|
||||||
|
|
||||||
/* Example of (static) statedata, real code would poll state */
|
/* 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-data-writes>0</denied-data-writes>"
|
||||||
"<denied-operations>0</denied-operations>"
|
"<denied-operations>0</denied-operations>"
|
||||||
"<denied-notifications>0</denied-notifications>"
|
"<denied-notifications>0</denied-notifications>"
|
||||||
|
|
|
||||||
|
|
@ -45,10 +45,29 @@
|
||||||
*/
|
*/
|
||||||
#define NACM_RECOVERY_USER "_nacm_recovery"
|
#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
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
int nacm_access(clicon_handle h, char *rpc, char *module,
|
int nacm_rpc(char *rpc, char *module, char *username, cxobj *xnacm, cbuf *cbret);
|
||||||
char *username, 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 */
|
#endif /* _CLIXON_NACM_H */
|
||||||
|
|
|
||||||
|
|
@ -138,6 +138,7 @@ int xml_purge(cxobj *xc);
|
||||||
int xml_child_rm(cxobj *xp, int i);
|
int xml_child_rm(cxobj *xp, int i);
|
||||||
int xml_rm(cxobj *xc);
|
int xml_rm(cxobj *xc);
|
||||||
int xml_rootchild(cxobj *xp, int i, cxobj **xcp);
|
int xml_rootchild(cxobj *xp, int i, cxobj **xcp);
|
||||||
|
int xml_rootchild_node(cxobj *xp, cxobj *xc);
|
||||||
|
|
||||||
char *xml_body(cxobj *xn);
|
char *xml_body(cxobj *xn);
|
||||||
cxobj *xml_body_get(cxobj *xn);
|
cxobj *xml_body_get(cxobj *xn);
|
||||||
|
|
|
||||||
|
|
@ -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);
|
typedef int (xmldb_get_t)(xmldb_handle xh, const char *db, char *xpath, int config, cxobj **xtop);
|
||||||
|
|
||||||
/* Type of xmldb put function */
|
/* 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 */
|
/* Type of xmldb copy function */
|
||||||
typedef int (xmldb_copy_t)(xmldb_handle xh, const char *from, const char *to);
|
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_getopt(clicon_handle h, char *optname, void **value);
|
||||||
int xmldb_setopt(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_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_copy(clicon_handle h, const char *from, const char *to);
|
||||||
int xmldb_lock(clicon_handle h, const char *db, int pid);
|
int xmldb_lock(clicon_handle h, const char *db, int pid);
|
||||||
int xmldb_unlock(clicon_handle h, const char *db);
|
int xmldb_unlock(clicon_handle h, const char *db);
|
||||||
|
|
|
||||||
|
|
@ -72,13 +72,16 @@
|
||||||
* Incoming RPC Message Validation Step 7 (c)
|
* Incoming RPC Message Validation Step 7 (c)
|
||||||
* The rule's "access-operations" leaf has the "exec" bit set or
|
* The rule's "access-operations" leaf has the "exec" bit set or
|
||||||
* has the special value "*".
|
* 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 0 No match
|
||||||
* @retval 1 Match
|
* @retval 1 Match
|
||||||
* @note access_operations is bit-fields
|
* @note access_operations is bit-fields
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
nacm_match_access(char *access_operations,
|
match_access(char *access_operations,
|
||||||
char *mode)
|
char *mode,
|
||||||
|
char *mode2)
|
||||||
{
|
{
|
||||||
if (access_operations==NULL)
|
if (access_operations==NULL)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -86,33 +89,21 @@ nacm_match_access(char *access_operations,
|
||||||
return 1;
|
return 1;
|
||||||
if (strstr(access_operations, mode)!=NULL)
|
if (strstr(access_operations, mode)!=NULL)
|
||||||
return 1;
|
return 1;
|
||||||
|
if (mode2 && strstr(access_operations, mode2)!=NULL)
|
||||||
|
return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Match nacm single rule. Either match with access or deny. Or not match.
|
/*! Match nacm single rule. Either match with access or deny. Or not match.
|
||||||
* @param[in] h Clicon handle
|
* @param[in] rpc rpc name
|
||||||
* @param[in] name rpc name
|
* @param[in] module Yang module name
|
||||||
* @param[in] xrule NACM rule XML tree
|
* @param[in] xrule NACM rule XML tree
|
||||||
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @retval 0 Matching rule AND Not access and cbret set
|
* @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
|
* @retval 2 No matching rule Goto step 10
|
||||||
* From RFC8341 3.4.4. Incoming RPC Message Validation
|
* @see 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 |
|
|
||||||
|
|
||||||
7.(cont) A rule matches if all of the following criteria are met:
|
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 rule's "module-name" leaf is "*" or equals the name of
|
||||||
the YANG module where the protocol operation is defined.
|
the YANG module where the protocol operation is defined.
|
||||||
|
|
@ -126,105 +117,83 @@ nacm_match_access(char *access_operations,
|
||||||
has the special value "*".
|
has the special value "*".
|
||||||
*/
|
*/
|
||||||
static int
|
static int
|
||||||
nacm_match_rule(clicon_handle h,
|
nacm_rule_rpc(char *rpc,
|
||||||
char *name,
|
char *module,
|
||||||
char *module,
|
cxobj *xrule)
|
||||||
cxobj *xrule,
|
|
||||||
cbuf *cbret)
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
char *module_rule; /* rule module name */
|
char *module_rule; /* rule module name */
|
||||||
char *rpc_rule;
|
char *rpc_rule;
|
||||||
char *access_operations;
|
char *access_operations;
|
||||||
char *action;
|
|
||||||
|
|
||||||
module_rule = xml_find_body(xrule, "module-name");
|
/* 7a) The rule's "module-name" leaf is "*" or equals the name of
|
||||||
rpc_rule = xml_find_body(xrule, "rpc-name");
|
the YANG module where the protocol operation is defined. */
|
||||||
/* XXX access_operations can be a set of bits */
|
if ((module_rule = xml_find_body(xrule, "module-name")) == NULL)
|
||||||
access_operations = xml_find_body(xrule, "access-operations");
|
goto nomatch;
|
||||||
action = xml_find_body(xrule, "action");
|
if (strcmp(module_rule,"*") && strcmp(module_rule,module))
|
||||||
clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__,
|
goto nomatch;
|
||||||
module_rule, rpc_rule, access_operations, action);
|
/* 7b) Either (1) the rule does not have a "rule-type" defined or
|
||||||
if (module_rule &&
|
(2) the "rule-type" is "protocol-operation" and the
|
||||||
(strcmp(module_rule,"*")==0 || strcmp(module_rule,module)==0)){
|
"rpc-name" is "*" or equals the name of the requested
|
||||||
if (nacm_match_access(access_operations, "exec")){
|
protocol operation. */
|
||||||
if (rpc_rule==NULL ||
|
if ((rpc_rule = xml_find_body(xrule, "rpc-name")) == NULL){
|
||||||
strcmp(rpc_rule, "*")==0 || strcmp(rpc_rule, name)==0){
|
if (xml_find_body(xrule, "path") || xml_find_body(xrule, "notification-name"))
|
||||||
/* Here is a matching rule */
|
goto nomatch;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
|
nomatch:
|
||||||
|
retval = 0;
|
||||||
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Process a nacm message control
|
/*! Process nacm incoming RPC message validation steps
|
||||||
* @param[in] h Clicon handle
|
* @param[in] module Yang module name
|
||||||
* @param[in] mode NACMmode, internal or external
|
* @param[in] rpc rpc name
|
||||||
* @param[in] name rpc name
|
* @param[in] username User name of requestor
|
||||||
* @param[in] username
|
* @param[in] xnacm NACM xml tree
|
||||||
* @param[in] xtop
|
|
||||||
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @retval 0 Not access and cbret set
|
* @retval 0 Not access and cbret set
|
||||||
* @retval 1 Access
|
* @retval 1 Access
|
||||||
* @see RFC8341 3.4.4. Incoming RPC Message Validation
|
* @see RFC8341 3.4.4. Incoming RPC Message Validation
|
||||||
|
* @see nacm_datanode_write
|
||||||
|
* @see nacm_datanode_read
|
||||||
*/
|
*/
|
||||||
static int
|
int
|
||||||
nacm_rpc_validation(clicon_handle h,
|
nacm_rpc(char *rpc,
|
||||||
char *name,
|
char *module,
|
||||||
char *module,
|
char *username,
|
||||||
char *username,
|
cxobj *xnacm,
|
||||||
cxobj *xtop,
|
cbuf *cbret)
|
||||||
cbuf *cbret)
|
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cxobj *xacm;
|
|
||||||
cxobj *x;
|
|
||||||
cxobj *xrule;
|
cxobj *xrule;
|
||||||
char *enabled = NULL;
|
|
||||||
cxobj **gvec = NULL; /* groups */
|
cxobj **gvec = NULL; /* groups */
|
||||||
size_t glen;
|
size_t glen;
|
||||||
cxobj *xrlist;
|
cxobj *rlist;
|
||||||
cxobj **rlistvec = NULL; /* rule-list */
|
cxobj **rlistvec = NULL; /* rule-list */
|
||||||
size_t rlistlen;
|
size_t rlistlen;
|
||||||
cxobj **rvec = NULL; /* rules */
|
cxobj **rvec = NULL; /* rules */
|
||||||
size_t rlen;
|
size_t rlen;
|
||||||
int ret;
|
|
||||||
int i, j;
|
int i, j;
|
||||||
char *exec_default = NULL;
|
char *exec_default = NULL;
|
||||||
|
char *gname;
|
||||||
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
|
char *action;
|
||||||
operation is permitted. (or config does not exist) */
|
int match= 0;
|
||||||
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;
|
|
||||||
|
|
||||||
/* 3. If the requested operation is the NETCONF <close-session>
|
/* 3. If the requested operation is the NETCONF <close-session>
|
||||||
protocol operation, then the protocol operation is permitted.
|
protocol operation, then the protocol operation is permitted.
|
||||||
*/
|
*/
|
||||||
if (strcmp(name, "close-session") == 0)
|
if (strcmp(rpc, "close-session") == 0)
|
||||||
goto permit;
|
goto permit;
|
||||||
/* 4. Check all the "group" entries to see if any of them contain a
|
/* 4. Check all the "group" entries to see if any of them contain a
|
||||||
"user-name" entry that equals the username for the session
|
"user-name" entry that equals the username for the session
|
||||||
|
|
@ -234,7 +203,7 @@ nacm_rpc_validation(clicon_handle h,
|
||||||
if (username == NULL)
|
if (username == NULL)
|
||||||
goto step10;
|
goto step10;
|
||||||
/* User's group */
|
/* 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;
|
goto done;
|
||||||
/* 5. If no groups are found, continue with step 10. */
|
/* 5. If no groups are found, continue with step 10. */
|
||||||
if (glen == 0)
|
if (glen == 0)
|
||||||
|
|
@ -243,15 +212,14 @@ nacm_rpc_validation(clicon_handle h,
|
||||||
configuration. If a rule-list's "group" leaf-list does not
|
configuration. If a rule-list's "group" leaf-list does not
|
||||||
match any of the user's groups, proceed to the next rule-list
|
match any of the user's groups, proceed to the next rule-list
|
||||||
entry. */
|
entry. */
|
||||||
if (xpath_vec(xacm, "rule-list", &rlistvec, &rlistlen) < 0)
|
if (xpath_vec(xnacm, "rule-list", &rlistvec, &rlistlen) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
for (i=0; i<rlistlen; i++){
|
for (i=0; i<rlistlen; i++){
|
||||||
xrlist = rlistvec[i];
|
rlist = rlistvec[i];
|
||||||
/* Loop through user's group to find match in this rule-list */
|
/* Loop through user's group to find match in this rule-list */
|
||||||
for (j=0; j<glen; j++){
|
for (j=0; j<glen; j++){
|
||||||
char *gname;
|
|
||||||
gname = xml_find_body(gvec[j], "name");
|
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 */
|
break; /* found */
|
||||||
}
|
}
|
||||||
if (j==glen) /* not 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
|
until a rule that matches the requested access operation is
|
||||||
found.
|
found.
|
||||||
*/
|
*/
|
||||||
if (xpath_vec(xrlist, "rule", &rvec, &rlen) < 0)
|
if (xpath_vec(rlist, "rule", &rvec, &rlen) < 0)
|
||||||
goto done;
|
goto done;
|
||||||
for (j=0; j<rlen; j++){
|
for (j=0; j<rlen; j++){
|
||||||
xrule = rvec[j];
|
xrule = rvec[j];
|
||||||
/* -1 error, 0 deny, 1 permit, 2 continue */
|
if ((match = nacm_rule_rpc(rpc, module, xrule)) < 0)
|
||||||
if ((ret = nacm_match_rule(h, name, module, xrule, cbret)) < 0)
|
|
||||||
goto done;
|
goto done;
|
||||||
switch(ret){
|
if (match)
|
||||||
case 0: /* deny */
|
|
||||||
goto deny;
|
|
||||||
break;
|
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:
|
step10:
|
||||||
/* 10. If the requested protocol operation is defined in a YANG module
|
/* 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
|
/* 11. If the requested protocol operation is the NETCONF
|
||||||
<kill-session> or <delete-config>, then the protocol operation
|
<kill-session> or <delete-config>, then the protocol operation
|
||||||
is denied. */
|
is denied. */
|
||||||
if (strcmp(name, "kill-session")==0 || strcmp(name, "delete-config")==0){
|
if (strcmp(rpc, "kill-session")==0 || strcmp(rpc, "delete-config")==0){
|
||||||
if (netconf_access_denied(cbret, "protocol", "default deny") < 0)
|
if (netconf_access_denied(cbret, "application", "default deny") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto deny;
|
goto deny;
|
||||||
}
|
}
|
||||||
/* 12. If the "exec-default" leaf is set to "permit", then permit the
|
/* 12. If the "exec-default" leaf is set to "permit", then permit the
|
||||||
protocol operation; otherwise, deny the request. */
|
protocol operation; otherwise, deny the request. */
|
||||||
|
exec_default = xml_find_body(xnacm, "exec-default");
|
||||||
if (exec_default ==NULL || strcmp(exec_default, "permit")==0)
|
if (exec_default ==NULL || strcmp(exec_default, "permit")==0)
|
||||||
goto permit;
|
goto permit;
|
||||||
if (netconf_access_denied(cbret, "protocol", "default deny") < 0)
|
if (netconf_access_denied(cbret, "application", "default deny") < 0)
|
||||||
goto done;
|
goto done;
|
||||||
goto deny;
|
goto deny;
|
||||||
permit:
|
permit:
|
||||||
|
|
@ -316,56 +294,617 @@ nacm_rpc_validation(clicon_handle h,
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*! Make nacm access control
|
/*---------------------------------------------------------------
|
||||||
* @param[in] h Clicon handle
|
* Datanode/module read and write
|
||||||
* @param[in] name rpc name
|
*/
|
||||||
* @param[in] username
|
|
||||||
|
/*! 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.
|
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||||
* @retval -1 Error
|
* @retval -1 Error
|
||||||
* @retval 0 Not access and cbret set
|
* @retval 0 Not access and cbret set
|
||||||
* @retval 1 Access
|
* @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
|
int
|
||||||
nacm_access(clicon_handle h,
|
nacm_datanode_write(cxobj *xt,
|
||||||
char *rpc,
|
cxobj *xr,
|
||||||
char *module,
|
enum nacm_access access,
|
||||||
char *username,
|
char *username,
|
||||||
cbuf *cbret)
|
cxobj *xnacm,
|
||||||
|
cbuf *cbret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
cxobj *xtop = NULL;
|
cxobj **gvec = NULL; /* groups */
|
||||||
char *mode;
|
size_t glen;
|
||||||
|
cxobj **rlistvec = NULL; /* rule-list */
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
size_t rlistlen;
|
||||||
mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
cxobj *rlist;
|
||||||
if (mode == NULL || strcmp(mode, "disabled") == 0){
|
cxobj **rvec = NULL; /* rules */
|
||||||
retval = 1;
|
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;
|
goto done;
|
||||||
}
|
/* 4. If no groups are found, continue with step 9. */
|
||||||
|
if (glen == 0)
|
||||||
/* 0. If nacm-mode is external, get NACM defintion from separet tree,
|
goto step9;
|
||||||
otherwise get it from internal configuration */
|
/* 5. Process all rule-list entries, in the order they appear in the
|
||||||
if (strcmp(mode, "external")==0){
|
configuration. If a rule-list's "group" leaf-list does not
|
||||||
if ((xtop = clicon_nacm_ext(h)) == NULL){
|
match any of the user's groups, proceed to the next rule-list
|
||||||
clicon_err(OE_XML, 0, "No nacm external tree");
|
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;
|
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 (match){
|
||||||
if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0)
|
if ((action = xml_find_body(xrule, "action")) == NULL)
|
||||||
goto done;
|
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{
|
/* 8. At this point, no matching rule was found in any rule-list
|
||||||
clicon_err(OE_UNIX, 0, "Invalid NACM mode: %s", mode);
|
entry. */
|
||||||
goto done;
|
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 */
|
permit:
|
||||||
if ((retval = nacm_rpc_validation(h, rpc, module, username, xtop, cbret)) < 0)
|
retval = 1;
|
||||||
goto done;
|
|
||||||
done:
|
done:
|
||||||
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
|
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
|
||||||
if (strcmp(mode, "internal")==0 && xtop)
|
if (gvec)
|
||||||
xml_free(xtop);
|
free(gvec);
|
||||||
|
if (rlistvec)
|
||||||
|
free(rlistvec);
|
||||||
|
if (rvec)
|
||||||
|
free(rvec);
|
||||||
return retval;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -640,7 +640,7 @@ xml_child_each(cxobj *xparent,
|
||||||
cxobj *xprev,
|
cxobj *xprev,
|
||||||
enum cxobj_type type)
|
enum cxobj_type type)
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
cxobj *xn = NULL;
|
cxobj *xn = NULL;
|
||||||
|
|
||||||
for (i=xprev?xprev->_x_vector_i+1:0; i<xparent->x_childvec_len; i++){
|
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>
|
* # Here xt will be: <a>2</a>
|
||||||
* @endcode
|
* @endcode
|
||||||
* @see xml_child_rm
|
* @see xml_child_rm
|
||||||
|
* @see xml_child_rootchild_node where xc is explicitly given
|
||||||
*/
|
*/
|
||||||
int
|
int
|
||||||
xml_rootchild(cxobj *xp,
|
xml_rootchild(cxobj *xp,
|
||||||
|
|
@ -984,6 +985,44 @@ xml_rootchild(cxobj *xp,
|
||||||
return retval;
|
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.
|
/*! Get the first sub-node which is an XML body.
|
||||||
* @param[in] xn xml tree node
|
* @param[in] xn xml tree node
|
||||||
* @retval The returned body as a pointer to the name string
|
* @retval The returned body as a pointer to the name string
|
||||||
|
|
|
||||||
|
|
@ -379,6 +379,7 @@ xmldb_get(clicon_handle h,
|
||||||
* @param[in] db running or candidate
|
* @param[in] db running or candidate
|
||||||
* @param[in] op Top-level operation, can be superceded by other op in tree
|
* @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] 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
|
* @param[out] cbret Initialized cligen buffer. On exit contains XML if retval == 0
|
||||||
* @retval 1 OK
|
* @retval 1 OK
|
||||||
* @retval 0 Failed, cbret contains error xml message
|
* @retval 0 Failed, cbret contains error xml message
|
||||||
|
|
@ -389,7 +390,7 @@ xmldb_get(clicon_handle h,
|
||||||
* cxobj *xret = NULL;
|
* cxobj *xret = NULL;
|
||||||
* if (xml_parse_string("<a>17</a>", yspec, &xt) < 0)
|
* if (xml_parse_string("<a>17</a>", yspec, &xt) < 0)
|
||||||
* err;
|
* 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;
|
* err;
|
||||||
* if (ret==0)
|
* if (ret==0)
|
||||||
* cbret contains netconf error message
|
* cbret contains netconf error message
|
||||||
|
|
@ -404,6 +405,7 @@ xmldb_put(clicon_handle h,
|
||||||
const char *db,
|
const char *db,
|
||||||
enum operation_type op,
|
enum operation_type op,
|
||||||
cxobj *xt,
|
cxobj *xt,
|
||||||
|
char *username,
|
||||||
cbuf *cbret)
|
cbuf *cbret)
|
||||||
{
|
{
|
||||||
int retval = -1;
|
int retval = -1;
|
||||||
|
|
@ -434,7 +436,7 @@ xmldb_put(clicon_handle h,
|
||||||
cbuf_free(cb);
|
cbuf_free(cb);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
retval = xa->xa_put_fn(xh, db, op, xt, cbret);
|
retval = xa->xa_put_fn(xh, db, op, xt, username, cbret);
|
||||||
done:
|
done:
|
||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2117,7 +2117,7 @@ api_path2xml(char *api_path,
|
||||||
int nvec;
|
int nvec;
|
||||||
cxobj *xroot;
|
cxobj *xroot;
|
||||||
|
|
||||||
clicon_debug(1, "%s", __FUNCTION__);
|
clicon_debug(1, "%s api_path:%s", __FUNCTION__, api_path);
|
||||||
if (*api_path!='/'){
|
if (*api_path!='/'){
|
||||||
clicon_err(OE_XML, EINVAL, "Invalid api-path: %s (must start with '/')",
|
clicon_err(OE_XML, EINVAL, "Invalid api-path: %s (must start with '/')",
|
||||||
api_path);
|
api_path);
|
||||||
|
|
|
||||||
|
|
@ -1389,6 +1389,7 @@ ys_populate_identity(yang_stmt *ys,
|
||||||
/* Iterate through all base statements and check the base identity exists
|
/* Iterate through all base statements and check the base identity exists
|
||||||
* AND populate the base identity recursively
|
* AND populate the base identity recursively
|
||||||
*/
|
*/
|
||||||
|
yc = NULL;
|
||||||
while ((yc = yn_each((yang_node*)ys, yc)) != NULL) {
|
while ((yc = yn_each((yang_node*)ys, yc)) != NULL) {
|
||||||
if (yc->ys_keyword != Y_BASE)
|
if (yc->ys_keyword != Y_BASE)
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -475,6 +475,7 @@ cv_validate1(cg_var *cv,
|
||||||
if (restype){
|
if (restype){
|
||||||
if (strcmp(restype, "enumeration") == 0){
|
if (strcmp(restype, "enumeration") == 0){
|
||||||
found = 0;
|
found = 0;
|
||||||
|
yi = NULL;
|
||||||
while ((yi = yn_each((yang_node*)yrestype, yi)) != NULL){
|
while ((yi = yn_each((yang_node*)yrestype, yi)) != NULL){
|
||||||
if (yi->ys_keyword != Y_ENUM)
|
if (yi->ys_keyword != Y_ENUM)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -500,6 +501,7 @@ cv_validate1(cg_var *cv,
|
||||||
if ((v = vec[i]) == NULL || !strlen(v))
|
if ((v = vec[i]) == NULL || !strlen(v))
|
||||||
continue;
|
continue;
|
||||||
found = 0;
|
found = 0;
|
||||||
|
yi = NULL;
|
||||||
while ((yi = yn_each((yang_node*)yrestype, yi)) != NULL){
|
while ((yi = yn_each((yang_node*)yrestype, yi)) != NULL){
|
||||||
if (yi->ys_keyword != Y_BIT)
|
if (yi->ys_keyword != Y_BIT)
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@ application. Assumes setup of http daemon as describe under apps/restonf
|
||||||
- site.sh Add your site-specific modifications here
|
- site.sh Add your site-specific modifications here
|
||||||
- test_nacm.sh Auth tests using internal NACM
|
- test_nacm.sh Auth tests using internal NACM
|
||||||
- test_nacm_ext.sh Auth tests using external NACM (separate file)
|
- 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_cli.sh CLI tests
|
||||||
- test_netconf.sh Netconf tests
|
- test_netconf.sh Netconf tests
|
||||||
- test_restconf.sh Restconf tests
|
- test_restconf.sh Restconf tests
|
||||||
|
|
|
||||||
|
|
@ -149,14 +149,17 @@ expectfn(){
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#
|
||||||
expecteq(){
|
expecteq(){
|
||||||
ret=$1
|
ret=$1
|
||||||
expect=$2
|
expect=$2
|
||||||
|
# echo "ret:$ret"
|
||||||
|
# echo "expect:$expect"
|
||||||
if [ -z "$ret" -a -z "$expect" ]; then
|
if [ -z "$ret" -a -z "$expect" ]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if [[ "$ret" = "$expect" ]]; then
|
if [[ "$ret" = "$expect" ]]; then
|
||||||
echo
|
echo
|
||||||
else
|
else
|
||||||
err "$expect" "$ret"
|
err "$expect" "$ret"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -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>]]>]]>$'
|
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
|
# NYI
|
||||||
if false; then
|
if false; then # nyi
|
||||||
new "minmax: validate should fail"
|
new "minmax: validate should fail"
|
||||||
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
|
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><validate><source><candidate/></source></validate></rpc>]]>]]>" "^<rpc-reply><rpc-error/></rpc-reply>]]>]]>$"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,20 +9,20 @@ APPNAME=example
|
||||||
. ./nacm.sh
|
. ./nacm.sh
|
||||||
|
|
||||||
cfg=$dir/conf_yang.xml
|
cfg=$dir/conf_yang.xml
|
||||||
fyang=$dir/test.yang
|
fyang=$dir/nacm-example.yang
|
||||||
fyangerr=$dir/err.yang
|
|
||||||
|
|
||||||
cat <<EOF > $cfg
|
cat <<EOF > $cfg
|
||||||
<config>
|
<config>
|
||||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
<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>/usr/local/share/clixon</CLICON_YANG_DIR>
|
|
||||||
<CLICON_YANG_DIR>$IETFRFC</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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||||
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_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_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||||
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
<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_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||||
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
||||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||||
|
|
@ -33,10 +33,13 @@ cat <<EOF > $cfg
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > $fyang
|
cat <<EOF > $fyang
|
||||||
module $APPNAME{
|
module nacm-example{
|
||||||
yang-version 1.1;
|
yang-version 1.1;
|
||||||
namespace "urn:example:clixon";
|
namespace "urn:example:nacm";
|
||||||
prefix ex;
|
prefix nacm;
|
||||||
|
import clixon-example {
|
||||||
|
prefix ex;
|
||||||
|
}
|
||||||
import ietf-netconf-acm {
|
import ietf-netconf-acm {
|
||||||
prefix nacm;
|
prefix nacm;
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +55,7 @@ EOF
|
||||||
RULES=$(cat <<EOF
|
RULES=$(cat <<EOF
|
||||||
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
|
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
|
||||||
<enable-nacm>false</enable-nacm>
|
<enable-nacm>false</enable-nacm>
|
||||||
<read-default>deny</read-default>
|
<read-default>permit</read-default>
|
||||||
<write-default>deny</write-default>
|
<write-default>deny</write-default>
|
||||||
<exec-default>deny</exec-default>
|
<exec-default>deny</exec-default>
|
||||||
|
|
||||||
|
|
@ -100,20 +103,20 @@ RULES=$(cat <<EOF
|
||||||
$NADMIN
|
$NADMIN
|
||||||
|
|
||||||
</nacm>
|
</nacm>
|
||||||
<x xmlns="urn:example:clixon">0</x>
|
<x xmlns="urn:example:nacm">0</x>
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
new "test params: -f $cfg -y $fyang"
|
new "test params: -f $cfg"
|
||||||
|
|
||||||
if [ $BE -ne 0 ]; then
|
if [ $BE -ne 0 ]; then
|
||||||
new "kill old backend"
|
new "kill old backend"
|
||||||
sudo clixon_backend -zf $cfg -y $fyang
|
sudo clixon_backend -zf $cfg
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
new "start backend -s init -f $cfg -y $fyang"
|
new "start backend -s init -f $cfg"
|
||||||
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
|
sudo $clixon_backend -s init -f $cfg -D $DBG
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
|
|
@ -124,22 +127,19 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||||
|
|
||||||
sleep 1
|
sleep 1
|
||||||
new "start restconf daemon (-a is enable basic authentication)"
|
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
|
sleep $RCWAIT
|
||||||
|
|
||||||
new "restconf DELETE whole datastore"
|
|
||||||
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ""
|
|
||||||
|
|
||||||
new2 "auth get"
|
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"
|
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"
|
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)"
|
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"}}}
'
|
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"}}}
'
|
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)"
|
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
|
#----------------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)" ""
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
new "Kill restconf daemon"
|
||||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||||
|
|
|
||||||
|
|
@ -10,17 +10,16 @@ APPNAME=example
|
||||||
. ./nacm.sh
|
. ./nacm.sh
|
||||||
|
|
||||||
cfg=$dir/conf_yang.xml
|
cfg=$dir/conf_yang.xml
|
||||||
fyang=$dir/test.yang
|
fyang=$dir/nacm-example.yang
|
||||||
fyangerr=$dir/err.yang
|
|
||||||
nacmfile=$dir/nacmfile
|
nacmfile=$dir/nacmfile
|
||||||
|
|
||||||
# Note filter out example_backend_nacm.so in CLICON_BACKEND_REGEXP below
|
# Note filter out example_backend_nacm.so in CLICON_BACKEND_REGEXP below
|
||||||
cat <<EOF > $cfg
|
cat <<EOF > $cfg
|
||||||
<config>
|
<config>
|
||||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
<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>/usr/local/share/clixon</CLICON_YANG_DIR>
|
||||||
<CLICON_YANG_DIR>$IETFRFC</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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||||
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
|
||||||
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
|
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
|
||||||
|
|
@ -39,13 +38,13 @@ cat <<EOF > $cfg
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > $fyang
|
cat <<EOF > $fyang
|
||||||
module $APPNAME{
|
module nacm-example{
|
||||||
yang-version 1.1;
|
yang-version 1.1;
|
||||||
namespace "urn:example:my";
|
namespace "urn:example:nacm";
|
||||||
import clixon-example {
|
import clixon-example {
|
||||||
prefix ex;
|
prefix ex;
|
||||||
}
|
}
|
||||||
prefix my;
|
prefix nacm;
|
||||||
container authentication {
|
container authentication {
|
||||||
description "Example code for enabling www basic auth and some example
|
description "Example code for enabling www basic auth and some example
|
||||||
users";
|
users";
|
||||||
|
|
@ -77,7 +76,7 @@ EOF
|
||||||
cat <<EOF > $nacmfile
|
cat <<EOF > $nacmfile
|
||||||
<nacm>
|
<nacm>
|
||||||
<enable-nacm>true</enable-nacm>
|
<enable-nacm>true</enable-nacm>
|
||||||
<read-default>deny</read-default>
|
<read-default>permit</read-default>
|
||||||
<write-default>deny</write-default>
|
<write-default>deny</write-default>
|
||||||
<exec-default>deny</exec-default>
|
<exec-default>deny</exec-default>
|
||||||
|
|
||||||
|
|
@ -124,9 +123,10 @@ cat <<EOF > $nacmfile
|
||||||
$NADMIN
|
$NADMIN
|
||||||
|
|
||||||
</nacm>
|
</nacm>
|
||||||
|
<x xmlns="urn:example:nacm">0</x>
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
new "test params: -f $cfg -y $fyang"
|
new "test params: -f $cfg"
|
||||||
|
|
||||||
if [ $BE -ne 0 ]; then
|
if [ $BE -ne 0 ]; then
|
||||||
new "kill old backend -zf $cfg "
|
new "kill old backend -zf $cfg "
|
||||||
|
|
@ -135,9 +135,9 @@ if [ $BE -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
new "start backend -s init -f $cfg -y $fyang"
|
new "start backend -s init -f $cfg"
|
||||||
# start new backend
|
# 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
|
if [ $? -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
|
|
@ -147,19 +147,16 @@ new "kill old restconf daemon"
|
||||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||||
|
|
||||||
new "start restconf daemon (-a is enable http basic auth)"
|
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
|
sleep $RCWAIT
|
||||||
|
|
||||||
new "restconf DELETE whole datastore"
|
|
||||||
expecteq "$(curl -u andy:bar -sS -X DELETE http://localhost/restconf/data)" ""
|
|
||||||
|
|
||||||
new2 "auth get"
|
new2 "auth get"
|
||||||
expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/clixon-example:state)" '{"clixon-example:state": {"op": "42"}}
|
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"
|
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)"
|
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"}}}
'
|
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"}}}
'
|
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)"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
new "Kill restconf daemon"
|
||||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||||
|
|
|
||||||
292
test/test_nacm_module_read.sh
Executable file
292
test/test_nacm_module_read.sh
Executable 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
274
test/test_nacm_module_write.sh
Executable 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
|
||||||
|
|
@ -29,20 +29,20 @@ APPNAME=example
|
||||||
. ./nacm.sh
|
. ./nacm.sh
|
||||||
|
|
||||||
cfg=$dir/conf_yang.xml
|
cfg=$dir/conf_yang.xml
|
||||||
fyang=$dir/test.yang
|
fyang=$dir/nacm-example.yang
|
||||||
fyangerr=$dir/err.yang
|
|
||||||
|
|
||||||
cat <<EOF > $cfg
|
cat <<EOF > $cfg
|
||||||
<config>
|
<config>
|
||||||
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
|
<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>/usr/local/share/clixon</CLICON_YANG_DIR>
|
|
||||||
<CLICON_YANG_DIR>$IETFRFC</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_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
|
||||||
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_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_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
|
||||||
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
|
||||||
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
|
<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_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
|
||||||
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
|
||||||
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
|
||||||
|
|
@ -53,10 +53,13 @@ cat <<EOF > $cfg
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
cat <<EOF > $fyang
|
cat <<EOF > $fyang
|
||||||
module $APPNAME{
|
module nacm-example{
|
||||||
yang-version 1.1;
|
yang-version 1.1;
|
||||||
namespace "urn:example:clixon";
|
namespace "urn:example:nacm";
|
||||||
prefix ex;
|
prefix nacm;
|
||||||
|
import clixon-example {
|
||||||
|
prefix ex;
|
||||||
|
}
|
||||||
import ietf-netconf-acm {
|
import ietf-netconf-acm {
|
||||||
prefix nacm;
|
prefix nacm;
|
||||||
}
|
}
|
||||||
|
|
@ -72,7 +75,7 @@ RULES=$(cat <<EOF
|
||||||
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
|
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
|
||||||
<enable-nacm>false</enable-nacm>
|
<enable-nacm>false</enable-nacm>
|
||||||
<read-default>deny</read-default>
|
<read-default>deny</read-default>
|
||||||
<write-default>deny</write-default>
|
<write-default>permit</write-default>
|
||||||
<exec-default>deny</exec-default>
|
<exec-default>deny</exec-default>
|
||||||
|
|
||||||
$NGROUPS
|
$NGROUPS
|
||||||
|
|
@ -122,21 +125,21 @@ RULES=$(cat <<EOF
|
||||||
$NADMIN
|
$NADMIN
|
||||||
|
|
||||||
</nacm>
|
</nacm>
|
||||||
<x xmlns="urn:example:clixon">0</x>
|
<x xmlns="urn:example:nacm">0</x>
|
||||||
EOF
|
EOF
|
||||||
)
|
)
|
||||||
|
|
||||||
new "test params: -f $cfg -y $fyang"
|
new "test params: -f $cfg"
|
||||||
|
|
||||||
if [ $BE -ne 0 ]; then
|
if [ $BE -ne 0 ]; then
|
||||||
new "kill old backend"
|
new "kill old backend"
|
||||||
sudo clixon_backend -zf $cfg -y $fyang
|
sudo clixon_backend -zf $cfg
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "start backend -s init -f $cfg -y $fyang"
|
new "start backend -s init -f $cfg"
|
||||||
sudo $clixon_backend -s init -f $cfg -y $fyang -D $DBG
|
sudo $clixon_backend -s init -f $cfg -D $DBG
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
err
|
err
|
||||||
fi
|
fi
|
||||||
|
|
@ -147,46 +150,48 @@ sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||||
|
|
||||||
sleep 1
|
sleep 1
|
||||||
new "start restconf daemon (-a is enable basic authentication)"
|
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
|
sleep $RCWAIT
|
||||||
|
|
||||||
new "auth set authentication config"
|
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"
|
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"
|
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)" ""
|
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"
|
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
|
# Rule 1: deny-kill-session
|
||||||
new "deny-kill-session: limited fail (netconf)"
|
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)"
|
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)"
|
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
|
# Rule 2: deny-delete-config
|
||||||
new "deny-delete-config: limited fail (netconf)"
|
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)"
|
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
|
# In restconf delete-config is translated to edit-config which is permitted
|
||||||
new "deny-delete-config: limited fail (restconf) ok"
|
new "deny-delete-config: limited fail (restconf) ok"
|
||||||
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" ''
|
expecteq "$(curl -u wilma:bar -sS -X DELETE http://localhost/restconf/data)" ''
|
||||||
|
|
||||||
new2 "admin get nacm (should be null)"
|
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)"
|
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
|
# Here the whole config is gone so we need to start again
|
||||||
new "auth set authentication config (restart)"
|
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"
|
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"
|
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)" ""
|
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
|
# Rule 3: permit-edit-config
|
||||||
new "permit-edit-config: limited ok restconf"
|
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"
|
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"
|
new "Kill restconf daemon"
|
||||||
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
sudo pkill -u www-data -f "/www-data/clixon_restconf"
|
||||||
|
|
|
||||||
|
|
@ -143,22 +143,22 @@ expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 '<rpc><edit-config><target><can
|
||||||
|
|
||||||
# LEAF_LISTS
|
# 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>]]>]]>$"
|
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>]]>]]>$"
|
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"
|
new "netconf commit"
|
||||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
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>]]>]]>$"
|
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"
|
new "netconf commit"
|
||||||
expecteof "$clixon_netconf -qf $cfg -y $fyang" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
|
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>]]>]]>$'
|
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
|
# LISTS
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,9 @@ module clixon-config {
|
||||||
leaf CLICON_YANG_MAIN_FILE {
|
leaf CLICON_YANG_MAIN_FILE {
|
||||||
type string;
|
type string;
|
||||||
description
|
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 {
|
leaf CLICON_YANG_MAIN_DIR {
|
||||||
type string;
|
type string;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue