* Experimental NACM RFC8341 Network Configuration Access Control Model.
* CLICON_NACM_MODE config option, default is disabled. * Added username attribute to all rpc:s from frontend to backend * Added NACM backend module in example
This commit is contained in:
parent
04a3f4db1b
commit
7650803475
32 changed files with 908 additions and 274 deletions
|
|
@ -347,7 +347,7 @@ from_client_edit_config(clicon_handle h,
|
|||
cbuf *cbx = NULL; /* Assist cbuf */
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec");
|
||||
clicon_err(OE_YANG, ENOENT, "No yang spec9");
|
||||
goto done;
|
||||
}
|
||||
if ((target = netconf_db_find(xn, "target")) == NULL){
|
||||
|
|
@ -803,7 +803,253 @@ from_client_debug(clicon_handle h,
|
|||
return retval;
|
||||
}
|
||||
|
||||
/*! Match nacm access operations according to RFC8321 3.4.4.
|
||||
* Incoming RPC Message Validation Step 7 (c)
|
||||
* The rule's "access-operations" leaf has the "exec" bit set or
|
||||
* has the special value "*".
|
||||
* @retval 0 No match
|
||||
* @retval 1 Match
|
||||
*/
|
||||
static int
|
||||
nacm_match_access(char *access_operations,
|
||||
char *mode)
|
||||
{
|
||||
if (access_operations==NULL)
|
||||
return 0;
|
||||
if (strcmp(access_operations,"*")==0)
|
||||
return 1;
|
||||
if (strstr(mode, access_operations)!=NULL)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Match nacm single rule. Either match with access or deny. Or not match.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] name rpc name
|
||||
* @param[in] xrule NACM rule XML tree
|
||||
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||
* @retval -1 Error
|
||||
* @retval 0 Matching rule AND Not access and cbret set
|
||||
* @retval 1 Matchung rule AND Access
|
||||
* @retval 2 No matching rule Goto step 10
|
||||
* From RFC8321 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:
|
||||
* The rule's "module-name" leaf is "*" or equals the name of
|
||||
the YANG module where the protocol operation is defined.
|
||||
|
||||
* Either (1) the rule does not have a "rule-type" defined or
|
||||
(2) the "rule-type" is "protocol-operation" and the
|
||||
"rpc-name" is "*" or equals the name of the requested
|
||||
protocol operation.
|
||||
|
||||
* The rule's "access-operations" leaf has the "exec" bit set or
|
||||
has the special value "*".
|
||||
*/
|
||||
static int
|
||||
nacm_match_rule(clicon_handle h,
|
||||
char *name,
|
||||
cxobj *xrule,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
// cxobj *x;
|
||||
char *module_name;
|
||||
char *rpc_name;
|
||||
char *access_operations;
|
||||
char *action;
|
||||
|
||||
module_name = xml_find_body(xrule, "module-name");
|
||||
rpc_name = xml_find_body(xrule, "rpc-name");
|
||||
access_operations = xml_find_body(xrule, "access-operations");
|
||||
action = xml_find_body(xrule, "action");
|
||||
clicon_debug(1, "%s: %s %s %s %s", __FUNCTION__,
|
||||
module_name, rpc_name, access_operations, action);
|
||||
if (module_name && strcmp(module_name,"*")==0){
|
||||
if (nacm_match_access(access_operations, "exec")){
|
||||
if (rpc_name==NULL ||
|
||||
strcmp(rpc_name, "*")==0 || strcmp(rpc_name, name)==0){
|
||||
/* Here is a matching rule */
|
||||
if (action && strcmp(action, "permit")==0){
|
||||
retval = 1;
|
||||
goto done;
|
||||
}
|
||||
else{
|
||||
if (netconf_access_denied(cbret, "protocol", "access denied") < 0)
|
||||
goto done;
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 2; /* no matching rule */
|
||||
done:
|
||||
return retval;
|
||||
|
||||
}
|
||||
|
||||
/*! Make nacm access control
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] name rpc name
|
||||
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
||||
* @retval -1 Error
|
||||
* @retval 0 Not access and cbret set
|
||||
* @retval 1 Access
|
||||
* From RFC8321 3.4.4. Incoming RPC Message Validation
|
||||
*/
|
||||
static int
|
||||
nacm_access(clicon_handle h,
|
||||
char *name,
|
||||
char *username,
|
||||
cbuf *cbret)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xtop = NULL;
|
||||
cxobj *xacm;
|
||||
cxobj *x;
|
||||
cxobj *xrlist;
|
||||
cxobj *xrule;
|
||||
char *enabled = NULL;
|
||||
cxobj **gvec = NULL; /* groups */
|
||||
size_t glen;
|
||||
cxobj **rlistvec = NULL; /* rule-list */
|
||||
size_t rlistlen;
|
||||
cxobj **rvec = NULL; /* rules */
|
||||
size_t rlen;
|
||||
int i, j;
|
||||
char *exec_default = NULL;
|
||||
int ret;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
/* 1. If the "enable-nacm" leaf is set to "false", then the protocol
|
||||
operation is permitted. (or config does not exist) */
|
||||
if (xmldb_get(h, "running", "nacm", 0, &xtop) < 0)
|
||||
goto done;
|
||||
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 */
|
||||
|
||||
/* 3. If the requested operation is the NETCONF <close-session>
|
||||
protocol operation, then the protocol operation is permitted.
|
||||
*/
|
||||
if (strcmp(name, "close-session") == 0)
|
||||
goto permit;
|
||||
/* 4. Check all the "group" entries to see if any of them contain a
|
||||
"user-name" entry that equals the username for the session
|
||||
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 step10;
|
||||
/* User's group */
|
||||
if (xpath_vec(xacm, "groups/group[user-name=%s]", &gvec, &glen, username) < 0)
|
||||
goto done;
|
||||
/* 5. If no groups are found, continue with step 10. */
|
||||
if (glen == 0)
|
||||
goto step10;
|
||||
/* 6. 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(xacm, "rule-list", &rlistvec, &rlistlen) < 0)
|
||||
goto done;
|
||||
for (i=0; i<rlistlen; i++){
|
||||
xrlist = rlistvec[i];
|
||||
/* Loop through user's group to find match in this rule-list */
|
||||
for (j=0; j<glen; j++){
|
||||
char *gname;
|
||||
gname = xml_find_body(gvec[j], "name");
|
||||
if (xpath_first(xrlist,".[group=%s]", gname)!=NULL)
|
||||
break; /* found */
|
||||
}
|
||||
if (j==glen) /* not found */
|
||||
continue;
|
||||
/* 7. For each rule-list entry found, process all rules, in order,
|
||||
until a rule that matches the requested access operation is
|
||||
found.
|
||||
*/
|
||||
if (xpath_vec(xrlist, "rule", &rvec, &rlen) < 0)
|
||||
goto done;
|
||||
for (j=0; j<rlen; j++){
|
||||
xrule = rvec[j];
|
||||
/* -1 error, 0 deny, 1 permit, 2 continue */
|
||||
if ((ret = nacm_match_rule(h, name, xrule, cbret)) < 0)
|
||||
goto done;
|
||||
switch(ret){
|
||||
case 0: /* deny */
|
||||
goto deny;
|
||||
break;
|
||||
case 1: /* permit */
|
||||
goto permit;
|
||||
break;
|
||||
case 2: /* no match, continue */
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
step10:
|
||||
/* 10. If the requested protocol operation is defined in a YANG module
|
||||
advertised in the server capabilities and the "rpc" statement
|
||||
contains a "nacm:default-deny-all" statement, then the protocol
|
||||
operation is denied. */
|
||||
/* 11. If the requested protocol operation is the NETCONF
|
||||
<kill-session> or <delete-config>, then the protocol operation
|
||||
is denied. */
|
||||
if (strcmp(name, "kill-session")==0 || strcmp(name, "delete-config")==0){
|
||||
if (netconf_access_denied(cbret, "protocol", "default deny") < 0)
|
||||
goto done;
|
||||
goto deny;
|
||||
}
|
||||
/* 12. If the "exec-default" leaf is set to "permit", then permit the
|
||||
protocol operation; otherwise, deny the request. */
|
||||
if (exec_default ==NULL || strcmp(exec_default, "permit")==0)
|
||||
goto permit;
|
||||
if (netconf_access_denied(cbret, "protocol", "default deny") < 0)
|
||||
goto done;
|
||||
goto deny;
|
||||
permit:
|
||||
retval = 1;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
|
||||
if (xtop)
|
||||
xml_free(xtop);
|
||||
if (gvec)
|
||||
free(gvec);
|
||||
if (rlistvec)
|
||||
free(rlistvec);
|
||||
if (rvec)
|
||||
free(rvec);
|
||||
return retval;
|
||||
deny: /* Here, cbret must contain a netconf error msg */
|
||||
assert(cbuf_len(cbret));
|
||||
retval = 0;
|
||||
goto done;
|
||||
}
|
||||
|
||||
/*! An internal clicon message has arrived from a client. Receive and dispatch.
|
||||
* @param[in] h Clicon handle
|
||||
* @param[in] s Socket where message arrived. read from this.
|
||||
* @param[in] arg Client entry (from).
|
||||
* @retval 0 OK
|
||||
|
|
@ -824,7 +1070,10 @@ from_client_msg(clicon_handle h,
|
|||
cbuf *cbret = NULL; /* return message */
|
||||
int pid;
|
||||
int ret;
|
||||
char *username;
|
||||
char *nacm_mode;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
pid = ce->ce_pid;
|
||||
/* Return netconf message. Should be filled in by the dispatch(sub) functions
|
||||
* as wither rpc-error or by positive response.
|
||||
|
|
@ -844,8 +1093,19 @@ from_client_msg(clicon_handle h,
|
|||
goto reply;
|
||||
}
|
||||
xe = NULL;
|
||||
username = xml_find_value(x, "username");
|
||||
while ((xe = xml_child_each(x, xe, CX_ELMNT)) != NULL) {
|
||||
name = xml_name(xe);
|
||||
clicon_debug(1, "%s name:%s", __FUNCTION__, name);
|
||||
#if 1 /* NACM */
|
||||
/* Make NACM access control if enabled as "internal"*/
|
||||
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
||||
if (nacm_mode && strcmp(nacm_mode,"internal") == 0)
|
||||
if ((ret = nacm_access(h, name, username, cbret)) < 0)
|
||||
goto done;
|
||||
if (!ret)
|
||||
goto reply;
|
||||
#endif
|
||||
if (strcmp(name, "get-config") == 0){
|
||||
if (from_client_get_config(h, xe, cbret) <0)
|
||||
goto done;
|
||||
|
|
@ -947,6 +1207,7 @@ from_client_msg(clicon_handle h,
|
|||
// ok:
|
||||
retval = 0;
|
||||
done:
|
||||
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
|
||||
if (xt)
|
||||
xml_free(xt);
|
||||
if (cbret)
|
||||
|
|
@ -976,6 +1237,7 @@ from_client(int s,
|
|||
clicon_handle h = ce->ce_handle;
|
||||
int eof;
|
||||
|
||||
clicon_debug(1, "%s", __FUNCTION__);
|
||||
// assert(s == ce->ce_s);
|
||||
if (clicon_msg_rcv(ce->ce_s, &msg, &eof) < 0)
|
||||
goto done;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue