NACM cleanup, uniform rule function, change of function names, etc.

This commit is contained in:
Olof hagsand 2019-02-02 11:35:50 +01:00
parent 8bf5cb0de5
commit 1e4022e73c
13 changed files with 180 additions and 247 deletions

View file

@ -76,13 +76,16 @@
* 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 Data node READ and WRITE access module support (RFC8341 3.4.5) * Incoming RPC Message validation is supported (3.4.4)
* Access control points added for `get`, `get-config`, `edit-config` in addition to incoming rpc. * Data Node Access validation is supported (3.4.5), except:
* RFC 8341 Example A.2 implemented, see: [test/test_nacm_module.sh] * rule-type data-node path is not supported
* Remaining work: data-node PATH * 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.
### 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 * Added `username` argument on `xmldb_put()` datastore function for NACM data-node write checks

View file

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

View file

@ -214,7 +214,7 @@ from_client_get_config(clicon_handle h,
goto ok; goto ok;
} }
/* Pre-NACM access step */ /* Pre-NACM access step */
if ((ret = nacm_access_h(h, username, &xnacm)) < 0) if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
goto done; goto done;
if (ret == 0){ /* Do NACM validation */ if (ret == 0){ /* Do NACM validation */
if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
@ -407,7 +407,7 @@ from_client_get(clicon_handle h,
goto ok; goto ok;
} }
/* Pre-NACM access step */ /* Pre-NACM access step */
if ((ret = nacm_access_h(h, username, &xnacm)) < 0) if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
goto done; goto done;
if (ret == 0){ /* Do NACM validation */ if (ret == 0){ /* Do NACM validation */
if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0) if (xpath_vec(xret, "%s", &xvec, &xlen, xpath?xpath:"/") < 0)
@ -728,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,
@ -1052,7 +1056,7 @@ from_client_msg(clicon_handle h,
clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc); clicon_debug(1, "%s module:%s rpc:%s", __FUNCTION__, module, rpc);
/* Pre-NACM access step */ /* Pre-NACM access step */
xnacm = NULL; xnacm = NULL;
if ((ret = nacm_access_h(h, username, &xnacm)) < 0) if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
goto done; goto done;
if (ret == 0){ /* Do NACM validation */ if (ret == 0){ /* Do NACM validation */
/* NACM rpc operation exec validation */ /* NACM rpc operation exec validation */

View file

@ -323,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,
@ -368,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,

View file

@ -306,6 +306,7 @@ You may also add a default method in the configuration file:
Yes. Systemd example files are provide for the backend and the 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

View file

@ -67,7 +67,7 @@ int nacm_rpc(char *rpc, char *module, char *username, cxobj *xnacm, cbuf *cbret)
int nacm_datanode_read(cxobj *xt, cxobj **xvec, size_t xlen, char *username, cxobj *nacm_xtree); int nacm_datanode_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, int nacm_datanode_write(cxobj *xt, cxobj *xr, enum nacm_access access,
char *username, cxobj *xnacm, cbuf *cbret); char *username, cxobj *xnacm, cbuf *cbret);
int nacm_access_h(clicon_handle h, char *username, cxobj **xnacmp); int nacm_access_pre(clicon_handle h, char *username, cxobj **xnacmp);
int nacm_access(char *mode, cxobj *xnacmin, char *username); int nacm_access(char *mode, cxobj *xnacmin, char *username);
#endif /* _CLIXON_NACM_H */ #endif /* _CLIXON_NACM_H */

View file

@ -101,23 +101,9 @@ match_access(char *access_operations,
* @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.
@ -133,44 +119,40 @@ match_access(char *access_operations,
static int static int
nacm_rule_rpc(char *rpc, nacm_rule_rpc(char *rpc,
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 (match_access(access_operations, "exec", NULL)){ protocol operation. */
if (rpc_rule==NULL || if ((rpc_rule = xml_find_body(xrule, "rpc-name")) == NULL){
strcmp(rpc_rule, "*")==0 || strcmp(rpc_rule, rpc)==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 nacm incoming RPC message validation steps /*! Process nacm incoming RPC message validation steps
@ -202,10 +184,11 @@ nacm_rpc(char *rpc,
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; char *gname;
char *action;
int match= 0;
/* 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.
@ -249,25 +232,30 @@ nacm_rpc(char *rpc,
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_rule_rpc(rpc, 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){ if (rvec){
free(rvec); free(rvec);
rvec=NULL; 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
advertised in the server capabilities and the "rpc" statement advertised in the server capabilities and the "rpc" statement
@ -277,7 +265,7 @@ nacm_rpc(char *rpc,
<kill-session> or <delete-config>, then the protocol operation <kill-session> or <delete-config>, then the protocol operation
is denied. */ is denied. */
if (strcmp(rpc, "kill-session")==0 || strcmp(rpc, "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;
} }
@ -286,7 +274,7 @@ nacm_rpc(char *rpc,
exec_default = xml_find_body(xnacm, "exec-default"); 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:
@ -307,45 +295,31 @@ nacm_rpc(char *rpc,
} }
/*--------------------------------------------------------------- /*---------------------------------------------------------------
* Datanode/module read * Datanode/module read and write
*/ */
/*! We have a rule matching user group. Now match proper read operation and module /*! We have a rule matching user group. Now match proper write operation and module
* @see RFC8341 3.4.5. Data Node Access Validation point (6)
* @retval -1 Error * @retval -1 Error
* @retval 0 No Match * @retval 0 No Match
* @retval 1 Match * @retval 1 Match
* @see rule_data_write * @see RFC8341 3.4.5. Data Node Access Validation point (6)
*/ */
static int static int
rule_data_read(cxobj *xrule, nacm_rule_datanode(cxobj *xt,
cxobj *xr, cxobj *xr,
cxobj *xt) cxobj *xrule,
enum nacm_access access)
{ {
int retval = -1; int retval = -1;
cxobj *xp; /* parent */ char *path;
char *access_operations; char *access_operations;
char *module_rule; /* rule module name */ char *module_rule; /* rule module name */
yang_stmt *ys; yang_stmt *ys;
yang_stmt *ymod; yang_stmt *ymod;
char *module; char *module;
char *path;
cxobj *xpath; /* xpath match */ cxobj *xpath; /* xpath match */
cxobj *xp; /* parent */
/* 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;
}
/* 6c) For a "read" access operation, the rule's "access-operations"
leaf has the "read" bit set or has the special value "*" */
access_operations = xml_find_body(xrule, "access-operations");
if (!match_access(access_operations, "read", NULL))
goto nomatch;
/* 6a) The rule's "module-name" leaf is "*" or equals the name of /* 6a) The rule's "module-name" leaf is "*" or equals the name of
* the YANG module where the requested data node is defined. */ * the YANG module where the requested data node is defined. */
if ((module_rule = xml_find_body(xrule, "module-name")) == NULL) if ((module_rule = xml_find_body(xrule, "module-name")) == NULL)
@ -358,7 +332,46 @@ rule_data_read(cxobj *xrule,
if (strcmp(module, module_rule) != 0) if (strcmp(module, module_rule) != 0)
goto nomatch; goto nomatch;
} }
/* Here module is matched, now check for path if any */
/* 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 (path){
if ((xpath = xpath_first(xt, "%s", path)) == NULL) if ((xpath = xpath_first(xt, "%s", path)) == NULL)
goto nomatch; goto nomatch;
@ -420,13 +433,13 @@ nacm_data_read_xr(cxobj *xt,
continue; continue;
/* 6. For each rule-list entry found, process all rules, in order, /* 6. For each rule-list entry found, process all rules, in order,
until a rule that matches the requested access operation is until a rule that matches the requested access operation is
found. (see 6 sub rules in rule_data_read) found. (see 6 sub rules in nacm_rule_datanode
*/ */
if (xpath_vec(rlist, "rule", &rvec, &rlen) < 0) if (xpath_vec(rlist, "rule", &rvec, &rlen) < 0)
goto done; goto done;
for (j=0; j<rlen; j++){ /* Loop through rules */ for (j=0; j<rlen; j++){ /* Loop through rules */
xrule = rvec[j]; xrule = rvec[j];
if ((match = rule_data_read(xrule, xr, xt)) < 0) if ((match = nacm_rule_datanode(xt, xr, xrule, NACM_READ)) < 0)
goto done; goto done;
if (match) /* xrule match */ if (match) /* xrule match */
break; break;
@ -616,106 +629,6 @@ nacm_datanode_read(cxobj *xt,
return retval; return retval;
} }
/*---------------------------------------------------------------
* Datanode/module write (=create, delete, update)
*/
/*! 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)
* @see rule_data_read
*/
static int
rule_data_write(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 */
/* 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;
}
/* 6c) For a "read" access operation, the rule's "access-operations"
leaf has the "read" bit set or has the special value "*" */
/* 6d) For a "create" access operation, the rule's
"access-operations" leaf has the "create" bit set or has the
special value "*".
6e) For a "delete" access operation, the rule's
"access-operations" leaf has the "delete" bit set or has the
special value "*".
6f) For an "update" access operation, the rule's
"access-operations" leaf has the "update" bit set or has the
special value "*". */
access_operations = xml_find_body(xrule, "access-operations");
switch (access){
case NACM_CREATE:
if (!match_access(access_operations, "create", "write"))
goto nomatch;
break;
case NACM_UPDATE:
if (!match_access(access_operations, "update", "write"))
goto nomatch;
break;
case NACM_DELETE:
if (!match_access(access_operations, "delete", "write"))
goto nomatch;
break;
default:
break;
}
/* 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;
}
/* Here module is matched, now check for path if any */
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;
}
/*! Make nacm datanode and module rule write access validation /*! Make nacm datanode and module rule write access validation
* The operations of NACM are: create, read, update, delete, exec * The operations of NACM are: create, read, update, delete, exec
* where write is short-hand for create+delete+update * where write is short-hand for create+delete+update
@ -790,17 +703,21 @@ nacm_datanode_write(cxobj *xt,
goto done; goto done;
/* 6. For each rule-list entry found, process all rules, in order, /* 6. For each rule-list entry found, process all rules, in order,
until a rule that matches the requested access operation is until a rule that matches the requested access operation is
found. (see 6 sub rules in nacm_match_rule2) found. (see 6 sub rules in nacm_rule_data_write)
*/ */
for (j=0; j<rlen; j++){ /* Loop through rules */ for (j=0; j<rlen; j++){ /* Loop through rules */
xrule = rvec[j]; xrule = rvec[j];
if ((match = rule_data_write(xt, xr, xrule, access)) < 0) if ((match = nacm_rule_datanode(xt, xr, xrule, access)) < 0)
goto done; goto done;
if (match) /* match */ if (match) /* match */
break; break;
} }
if (match) if (match)
break; break;
if (rvec){
free(rvec);
rvec = NULL;
}
} }
if (match){ if (match){
if ((action = xml_find_body(xrule, "action")) == NULL) if ((action = xml_find_body(xrule, "action")) == NULL)
@ -934,7 +851,7 @@ nacm_access(char *mode,
* @retval 1 OK permitted. You do not need to do next NACM step * @retval 1 OK permitted. You do not need to do next NACM step
* @code * @code
* cxobj *xnacm = NULL; * cxobj *xnacm = NULL;
* if ((ret = nacm_access_h(h, username, &xnacm)) < 0) * if ((ret = nacm_access_pre(h, username, &xnacm)) < 0)
* err; * err;
* if (ret == 0){ * if (ret == 0){
* // Next step NACM processing * // Next step NACM processing
@ -943,11 +860,10 @@ nacm_access(char *mode,
* @endcode * @endcode
* @see RFC8341 3.4 Access Control Enforcement Procedures * @see RFC8341 3.4 Access Control Enforcement Procedures
*/ */
int int
nacm_access_h(clicon_handle h, nacm_access_pre(clicon_handle h,
char *username, char *username,
cxobj **xnacmp) cxobj **xnacmp)
{ {
int retval = -1; int retval = -1;
char *mode; char *mode;

View file

@ -7,6 +7,9 @@ application. Assumes setup of http daemon as describe under apps/restonf
- site.sh Add your site-specific modifications here - 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

View file

@ -165,16 +165,16 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-exa
' '
new2 "guest get nacm" new2 "guest get nacm"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-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 '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-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 '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-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 '{"nacm-example:x": 3}' http://localhost/restconf/data/nacm-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"

View file

@ -177,16 +177,16 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-exa
' '
new2 "guest get nacm" new2 "guest get nacm"
expecteq "$(curl -u guest:bar -sS -X GET http://localhost/restconf/data/nacm-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 '{"nacm-example:x": 1}' http://localhost/restconf/data/nacm-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/nacm-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/nacm-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 show conf" 0 "^x 1;$" expectfn "$clixon_cli -1 -U andy -l o -f $cfg show conf" 0 "^x 1;$"

View file

@ -214,22 +214,22 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data)" '{"dat
#user:guest #user:guest
new2 "guest read fail" 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": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' 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" 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>protocol</error-type><error-tag>access-denied</error-tag><error-severity>error</error-severity><error-message>default deny</error-message></rpc-error></rpc-reply>]]>]]>$' 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" 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": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' 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" 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": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' 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" 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": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' 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)" new2 "guest read top ok (part)"
expecteq "$(curl -u guest:bar -sS -X GET 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 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 #------- RPC operation
@ -248,10 +248,10 @@ 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>]]>]]>$' 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" 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": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "access denied"}}} ' 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" 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>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 -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 #------------------ Set read-default permit
@ -267,7 +267,7 @@ expecteq "$(curl -u wilma:bar -sS -X GET http://localhost/restconf/data/nacm-exa
' '
new2 "guest read state fail" 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": "protocol","error-tag": "access-denied","error-severity": "error","error-message": "default deny"}}} ' 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" new "Kill restconf daemon"

View file

@ -184,7 +184,7 @@ MSG="<data>$RULES</data>"
new "update root list permit" 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")" '' expecteq "$(curl -u andy:bar -sS -H 'Content-Type: application/yang-data+xml' -X PUT http://localhost/restconf/data -d "$MSG")" ''
new "delete root list deny" 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"}}} ' 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" new "delete root permit"

View file

@ -171,20 +171,20 @@ expecteq "$(curl -u andy:bar -sS -X GET http://localhost/restconf/data/nacm-exam
# 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 -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 -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 -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 -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"
@ -212,7 +212,7 @@ new "permit-edit-config: limited ok restconf"
expecteq "$(curl -u wilma:bar -sS -X PUT -d '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-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 '{"nacm-example:x": 2}' http://localhost/restconf/data/nacm-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"