diff --git a/CHANGELOG.md b/CHANGELOG.md index 2931cd94..cc5a3ab3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,10 +26,9 @@ Expected: May 2020 ### Major New features -* NACM RFC341 datanode paths - * NACM datanode read paths - * NYI: NACM datanode paths for create/delete/update - +* NACM RFC341 datanode read and write paths + * This completes the NACM RPC and Data node access checks (notification still remains) + ### API changes on existing protocol/config features (You may have have to change how you use Clixon) * Stricter incoming RPC sanity checking, error messages may have changed. diff --git a/lib/clixon/clixon_xml.h b/lib/clixon/clixon_xml.h index 9ae2fb60..afda331a 100644 --- a/lib/clixon/clixon_xml.h +++ b/lib/clixon/clixon_xml.h @@ -137,7 +137,7 @@ typedef struct xml cxobj; /* struct defined in clicon_xml.c */ */ typedef int (xml_applyfn_t)(cxobj *x, void *arg); -typedef struct clixon_xml_vec clixon_xvec; /* struct defined in clicon_xvec.c */ +typedef struct clixon_xml_vec clixon_xvec; /* struct defined in clicon_xml_vec.c */ /* * xml_flag() flags: diff --git a/lib/src/clixon_datastore_write.c b/lib/src/clixon_datastore_write.c index bce8bf5b..4a7e56ca 100644 --- a/lib/src/clixon_datastore_write.c +++ b/lib/src/clixon_datastore_write.c @@ -208,6 +208,7 @@ check_body_namespace(cxobj *x0, * @param[in] y0 Yang spec corresponding to xml-node x0. NULL if x0 is NULL * @param[in] x0p Parent of x0 * @param[in] x1 XML tree which modifies base + * @param[in] x1t Request root node (nacm needs this) * @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 (only if !permit) @@ -229,6 +230,7 @@ text_modify(clicon_handle h, yang_stmt *y0, cxobj *x0p, cxobj *x1, + cxobj *x1t, enum operation_type op, char *username, cxobj *xnacm, @@ -317,7 +319,7 @@ text_modify(clicon_handle h, * of ordered-by user and (changed) insert attribute. */ if (!permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -334,7 +336,7 @@ text_modify(clicon_handle h, case OP_NONE: /* fall thru */ if (x0==NULL){ if ((op != OP_NONE) && !permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x1, NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -401,7 +403,7 @@ text_modify(clicon_handle h, x0bstr = xml_value(x0b); if (x0bstr==NULL || strcmp(x0bstr, x1bstr)){ if ((op != OP_NONE) && !permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x1, + if ((ret = nacm_datanode_write(h, x1t, x1, x0bstr==NULL?NACM_CREATE:NACM_UPDATE, username, xnacm, cbret)) < 0) goto done; @@ -427,7 +429,7 @@ text_modify(clicon_handle h, case OP_REMOVE: /* fall thru */ if (x0){ if ((op != OP_NONE) && !permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x0, NACM_DELETE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -489,7 +491,7 @@ text_modify(clicon_handle h, * of ordered-by user and (changed) insert attribute. */ if (!permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x1, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -515,7 +517,7 @@ text_modify(clicon_handle h, if (op == OP_NONE) break; if (op==OP_MERGE && !permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x0, x0?NACM_UPDATE:NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -532,7 +534,7 @@ text_modify(clicon_handle h, } /* anyxml, anydata */ if (x0==NULL){ if (op==OP_MERGE && !permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x1, NACM_CREATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x1, NACM_CREATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -603,7 +605,7 @@ text_modify(clicon_handle h, x1cname = xml_name(x1c); x0c = x0vec[i++]; yc = yang_find_datanode(y0, x1cname); - if ((ret = text_modify(h, x0c, yc, x0, x1c, op, + if ((ret = text_modify(h, x0c, yc, x0, x1c, x1t, op, username, xnacm, permit, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ @@ -624,7 +626,7 @@ text_modify(clicon_handle h, case OP_REMOVE: /* fall thru */ if (x0){ if (!permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x0, NACM_DELETE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -656,6 +658,7 @@ text_modify(clicon_handle h, * @param[in] h Clicon handle * @param[in] x0 Base xml tree (can be NULL in add scenarios) * @param[in] x1 XML tree which modifies base + * @param[in] x1t Request root node (nacm needs this) * @param[in] yspec Top-level yang spec (if y is NULL) * @param[in] op OP_MERGE, OP_REPLACE, OP_REMOVE, etc * @param[in] username User name of requestor for nacm @@ -671,6 +674,7 @@ static int text_modify_top(clicon_handle h, cxobj *x0, cxobj *x1, + cxobj *x1t, yang_stmt *yspec, enum operation_type op, char *username, @@ -705,7 +709,7 @@ text_modify_top(clicon_handle h, case OP_REMOVE: case OP_REPLACE: if (!permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x0, NACM_DELETE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x0, NACM_DELETE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -739,7 +743,7 @@ text_modify_top(clicon_handle h, /* Special case top-level replace */ else if (op == OP_REPLACE || op == OP_DELETE){ if (!permit && xnacm){ - if ((ret = nacm_datanode_write(h, NULL, x1, NACM_UPDATE, username, xnacm, cbret)) < 0) + if ((ret = nacm_datanode_write(h, x1t, x1, NACM_UPDATE, username, xnacm, cbret)) < 0) goto done; if (ret == 0) goto fail; @@ -773,7 +777,7 @@ text_modify_top(clicon_handle h, goto done; x0c = NULL; } - if ((ret = text_modify(h, x0c, yc, x0, x1c, op, + if ((ret = text_modify(h, x0c, yc, x0, x1c, x1t, op, username, xnacm, permit, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ @@ -917,7 +921,7 @@ xmldb_put(clicon_handle h, * Modify base tree x with modification x1. This is where the * new tree is made. */ - if ((ret = text_modify_top(h, x0, x1, yspec, op, username, xnacm, permit, cbret)) < 0) + if ((ret = text_modify_top(h, x0, x1, x1, yspec, op, username, xnacm, permit, cbret)) < 0) goto done; /* If xml return - ie netconf error xml tree, then stop and return OK */ if (ret == 0){ diff --git a/lib/src/clixon_nacm.c b/lib/src/clixon_nacm.c index 409e7965..25da7526 100644 --- a/lib/src/clixon_nacm.c +++ b/lib/src/clixon_nacm.c @@ -310,342 +310,6 @@ nacm_rpc(char *rpc, goto done; } -/*--------------------------------------------------------------- - * Datanode/module read and write - */ - -/*! There is a data-node rule with a path. Match it. - * @param[in] h Clixon handle - * @param[in] xt XML root tree with "config" label. - * @param[in] xr XML requestor node (part of xt) - * @param[in] pathobj XML nacm path node - * @retval -1 Error - * @retval 0 No Match - * @retval 1 Match - * @note not used for read - */ -static int -nacm_rule_datanode_path(clicon_handle h, - cxobj *xt, - cxobj *xreq, - cxobj *pathobj) -{ - int retval = -1; - cvec *nsc0 = NULL; /* Non-canonical namespace context */ - cxobj *xp; /* Node specified by path */ - cxobj *xa; /* Ancestor */ - char *path0; /* Non-canonical path */ - char *path=NULL; /* Canonical path */ - yang_stmt *yspec; - cxobj **xvec = NULL; - size_t xlen = 0; - int ret; - - yspec = clicon_dbspec_yang(h); - path0 = clixon_trim2(xml_body(pathobj), " \t\n"); - /* Create namespace context for with nacm namespace as default */ - if (xml_nsctx_node(pathobj, &nsc0) < 0) - goto done; - /* instance-id requires canonical paths */ - if (xpath2canonical(path0, nsc0, yspec, &path, NULL) < 0) - goto done; - if ((ret = clixon_xml_find_instance_id(xt, yspec, &xvec, &xlen, "%s", path)) < 0) - goto nomatch; - if (ret == 0) - goto nomatch; - assert(xlen == 1); /* XXX: This may be rich */ - /* xp is the node specified by the path */ - xp = xvec[0]; - - /* The requested node (xreq) is the node specified by the path (xp) or is a - * descendant node of the path xn. - * By checking if xreq is equal to xp or if any of its ancestors is = - * xmatch is one of xvec[] or an ancestor of the xvec[] nodes. - */ - xa = xreq; - do { - if (xp == xa) - goto match; - } while ((xa = xml_parent(xa)) != NULL); - match: - retval = 1; - done: - if (nsc0) - cvec_free(nsc0); - if (path) - free(path); - if (xvec) - free(xvec); - return retval; - nomatch: - retval = 0; - goto done; -} - -/*! We have a rule matching user group. Now match proper write operation and module - * @param[in] h Clixon handle - * @param[in] xt XML root tree with "config" label. - * @param[in] xreq XML requestor node (part of xt) - * @param[in] xrule XML rule node - * @param[in] access read/write/create/update/delete/exec - * @retval -1 Error - * @retval 0 No Match - * @retval 1 Match - * @see RFC8341 3.4.5. Data Node Access Validation point (6) - * @note read access is not handled in this function, see nacm_data_read_xrule - */ -static int -nacm_rule_datanode(clicon_handle h, - cxobj *xt, - cxobj *xreq, - cxobj *xrule, - enum nacm_access access) -{ - int retval = -1; - char *access_operations; - char *module_rule; /* rule module name */ - yang_stmt *ys; - yang_stmt *ymod; - char *module; - cxobj *pathobj; - int ret; - - /* 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 (xreq==NULL || (ys = xml_spec(xreq)) == NULL) - goto nomatch; - ymod = ys_module(ys); - module = yang_argument_get(ymod); - 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. */ - if ((pathobj = xml_find_type(xrule, NULL, "path", CX_ELMNT)) == 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: /* Not used, see nacm_data_read_xrule */ - /* 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 - * A path is considered to match if the requested node (xreq) is the node - * specified by the path or is a descendant node of the path */ - if (pathobj){ - if ((ret = nacm_rule_datanode_path(h, xt, xreq, pathobj)) < 0) - goto done; - if (ret == 0) - goto nomatch; - if (ret == 2) - goto submatch; - } - // match: - retval = 1; - done: - return retval; - nomatch: - retval = 0; - goto done; - submatch: - retval = 2; - goto done; -} - -/*! Perform NACM action: mark if permit, del if deny - * @param[in] xrule NACM rule - * @param[in] xn XML node (requested node) - * @retval -1 Error - * @retval 0 OK - */ -static int -nacm_data_read_action(cxobj *xrule, - cxobj *xn) -{ - int retval = -1; - char *action; - - if ((action = xml_find_body(xrule, "action")) != NULL){ - if (strcmp(action, "deny")==0) - xml_flag_set(xn, XML_FLAG_DEL); - else if (strcmp(action, "permit")==0) - xml_flag_set(xn, XML_FLAG_MARK); - } - retval = 0; - //done: - return retval; -} - -/*! Match specific rule to specific requested node - * @param[in] xt XML root tree with "config" label - * @param[in] xn XML node (requested node) - * @param[in] xrule NACM rule - * @param[in] yspec YANG spec - * @retval -1 Error - * @retval 0 OK and rule does not match - * @retval 1 OK and rule matches - * Two distinct cases: - * (1) read_default is permit - * mark all deny rules and remove them - * (2) read_default is deny: - * mark all permit rules and ancestors, remove everything else - */ -static int -nacm_data_read_xrule_xml(cxobj *xt, - cxobj *xn, - cxobj *xrule, - cxobj *xp, - yang_stmt *yspec) -{ - int retval = -1; - cxobj *pathobj; - yang_stmt *ymod; - char *module_rule; /* rule module name */ - - if ((module_rule = xml_find_body(xrule, "module-name")) == NULL) - goto nomatch; - /* 6a) The rule's "module-name" leaf is "*" or equals the name of - * the YANG module where the requested data node is defined. - */ - if (strcmp(module_rule, "*") != 0){ - if (ys_module_by_xml(yspec, xn, &ymod) < 0) - goto done; - if (strcmp(yang_argument_get(ymod), 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. */ - if ((pathobj = xml_find_type(xrule, NULL, "path", CX_ELMNT)) == NULL){ - if (nacm_data_read_action(xrule, xn) < 0) - goto done; - goto match; - } - /* Create namespace context for with nacm namespace as default */ - - /* path may not be yang-consistent gives an error,... but retval =0 back,... - * Here I may need another function. - * I have a top-level node "xt", a node "xn" and a "path". - * I want to know if "xn" is or is a descendant of the matches of path on xt. - * The brute force is to evaluate the result in xvec and then see if ancestors of - * xn is in xvec. - */ - - /* Check if ancestor is xp */ - if (xn != xp && !xml_isancestor(xn, xp)) - goto nomatch; - if (nacm_data_read_action(xrule, xn) < 0) - goto done; - match: - retval = 1; /* match */ - done: - return retval; - nomatch: - retval = 0; - goto done; -} - -/*! Recursive check for NACM read rules among all XML nodes - * @param[in] h Clicon handle - * @param[in] xt XML root tree with "config" label - * @param[in] xn XML node (requested node) - * @param[in] gvec Vector of groups - * @param[in] glen Length of group vector - * @param[in] rlistvec Vector of rules - * @param[in] rlistlen Length of rule vector - * @param[in] nsc Namespace context - * @param[in] yspec YANG spec - * @retval 0 OK - * @retval -1 Error - */ -static int -nacm_datanode_read_recurse(clicon_handle h, - cxobj *xt, - cxobj *xn, - cxobj **gvec, - size_t glen, - cxobj **rlistvec, - size_t rlistlen, - cvec *nsc, - clixon_xvec *rulevec, - clixon_xvec *xpathvec, - yang_stmt *yspec) -{ - int retval=-1; - cxobj *x; - int i; - cxobj *xprev; - int ret; - - if (xml_spec(xn)){ /* Check this node */ - for (i=0; i