Includes all error, log and debug messages See [Customized NETCONF error message](https://github.com/clicon/clixon/issues/454)
1349 lines
46 KiB
C
1349 lines
46 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2017-2019 Olof Hagsand
|
|
Copyright (C) 2020-2022 Olof Hagsand and Rubicon Communications, LLC(Netgate)
|
|
|
|
This file is part of CLIXON.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
the GNU General Public License Version 3 or later (the "GPL"),
|
|
in which case the provisions of the GPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of the GPL, and not to allow others to
|
|
use your version of this file under the terms of Apache License version 2,
|
|
indicate your decision by deleting the provisions above and replace them with
|
|
the notice and other provisions required by the GPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the Apache License version 2 or the GPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
|
|
* NACM code according to RFC8341 Network Configuration Access Control Model
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <limits.h>
|
|
#include <stdint.h>
|
|
#include <assert.h>
|
|
#include <syslog.h>
|
|
#include <sys/time.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clixon */
|
|
#include "clixon_queue.h"
|
|
#include "clixon_hash.h"
|
|
#include "clixon_string.h"
|
|
#include "clixon_handle.h"
|
|
#include "clixon_yang.h"
|
|
#include "clixon_xml.h"
|
|
#include "clixon_err.h"
|
|
#include "clixon_log.h"
|
|
#include "clixon_debug.h"
|
|
#include "clixon_options.h"
|
|
#include "clixon_data.h"
|
|
#include "clixon_netconf_lib.h"
|
|
#include "clixon_xpath_ctx.h"
|
|
#include "clixon_xpath.h"
|
|
#include "clixon_yang_module.h"
|
|
#include "clixon_datastore.h"
|
|
#include "clixon_xml_nsctx.h"
|
|
#include "clixon_xml_map.h"
|
|
#include "clixon_path.h"
|
|
#include "clixon_xml_vec.h"
|
|
#include "clixon_nacm.h"
|
|
|
|
/* NACM namespace for use with xml namespace contexts and xpath */
|
|
#define NACM_NS "urn:ietf:params:xml:ns:yang:ietf-netconf-acm"
|
|
|
|
/*! Match nacm access operations according to RFC8341 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 "*".
|
|
* @param[in] mode Primary mode, eg read, create, update, delete, exec
|
|
* @param[in] mode2 Secondary mode, eg "write"
|
|
* @retval 1 Match
|
|
* @retval 0 No match
|
|
* @note access_operations is bit-fields
|
|
*/
|
|
static int
|
|
match_access(char *access_operations,
|
|
char *mode,
|
|
char *mode2)
|
|
{
|
|
if (access_operations==NULL)
|
|
return 0;
|
|
if (strcmp(access_operations,"*")==0)
|
|
return 1;
|
|
if (strstr(access_operations, mode)!=NULL)
|
|
return 1;
|
|
if (mode2 && strstr(access_operations, mode2)!=NULL)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/*! Match nacm single rule. Either match with access or deny. Or not match.
|
|
*
|
|
* @param[in] rpc rpc name
|
|
* @param[in] module Yang module name
|
|
* @param[in] xrule NACM rule XML tree
|
|
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
|
* @retval 2 No matching rule Goto step 10
|
|
* @retval 1 Matching rule AND Access
|
|
* @retval 0 Matching rule AND Not access and cbret set
|
|
* @retval -1 Error
|
|
* @see RFC8341 3.4.4. Incoming RPC Message Validation
|
|
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_rule_rpc(char *rpc,
|
|
char *module,
|
|
cxobj *xrule)
|
|
{
|
|
int retval = -1;
|
|
char *module_rule; /* rule module name */
|
|
char *rpc_rule;
|
|
char *access_operations;
|
|
|
|
/* 7a) The rule's "module-name" leaf is "*" or equals the name of
|
|
the YANG module where the protocol operation is defined. */
|
|
if ((module_rule = xml_find_body(xrule, "module-name")) == NULL)
|
|
goto nomatch;
|
|
if (strcmp(module_rule,"*") && strcmp(module_rule,module))
|
|
goto nomatch;
|
|
/* 7b) 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. */
|
|
if ((rpc_rule = xml_find_body(xrule, "rpc-name")) == NULL){
|
|
if (xml_find_body(xrule, "path") || xml_find_body(xrule, "notification-name"))
|
|
goto nomatch;
|
|
}
|
|
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:
|
|
return retval;
|
|
nomatch:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Process nacm incoming RPC message validation steps
|
|
*
|
|
* @param[in] module Yang module name
|
|
* @param[in] rpc rpc name
|
|
* @param[in] username User name of requestor
|
|
* @param[in] xnacm NACM xml tree
|
|
* @param[out] cbret Cligen buffer result. Set to an error msg if retval=0.
|
|
* @retval 1 Access
|
|
* @retval 0 Not access and cbret set
|
|
* @retval -1 Error
|
|
* @see RFC8341 3.4.4. Incoming RPC Message Validation
|
|
* @see nacm_datanode_write
|
|
* @see nacm_datanode_read
|
|
*/
|
|
int
|
|
nacm_rpc(char *rpc,
|
|
char *module,
|
|
char *username,
|
|
cxobj *xnacm,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xrule;
|
|
cxobj **gvec = NULL; /* groups */
|
|
size_t glen;
|
|
cxobj *rlist;
|
|
cxobj **rlistvec = NULL; /* rule-list */
|
|
size_t rlistlen;
|
|
cxobj **rvec = NULL; /* rules */
|
|
size_t rlen;
|
|
int i, j;
|
|
char *exec_default = NULL;
|
|
char *gname;
|
|
char *action;
|
|
int match= 0;
|
|
cvec *nsc = NULL;
|
|
|
|
/* Create namespace context for with nacm namespace as default */
|
|
if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL)
|
|
goto done;
|
|
/* 3. If the requested operation is the NETCONF <close-session>
|
|
protocol operation, then the protocol operation is permitted.
|
|
*/
|
|
if (strcmp(rpc, "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(xnacm, nsc, "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(xnacm, nsc, "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, nsc, ".[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(rlist, nsc, "rule", &rvec, &rlen) < 0)
|
|
goto done;
|
|
for (j=0; j<rlen; j++){
|
|
xrule = rvec[j];
|
|
if ((match = nacm_rule_rpc(rpc, module, xrule)) < 0)
|
|
goto done;
|
|
if (match)
|
|
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:
|
|
/* 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(rpc, "kill-session")==0 || strcmp(rpc, "delete-config")==0){
|
|
if (netconf_access_denied(cbret, "application", "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. */
|
|
exec_default = xml_find_body(xnacm, "exec-default");
|
|
if (exec_default ==NULL || strcmp(exec_default, "permit")==0)
|
|
goto permit;
|
|
if (netconf_access_denied(cbret, "application", "default deny") < 0)
|
|
goto done;
|
|
goto deny;
|
|
permit:
|
|
retval = 1;
|
|
done:
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
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;
|
|
}
|
|
|
|
/* Local struct for keeping preparation/compiled data in NACM data path code */
|
|
struct prepvec{
|
|
qelem_t pv_q;
|
|
cxobj *pv_xrule;
|
|
clixon_xvec *pv_xpathvec;
|
|
};
|
|
typedef struct prepvec prepvec;
|
|
|
|
/*! Delete all Upgrade callbacks
|
|
*/
|
|
int
|
|
prepvec_free(prepvec *pv_list)
|
|
{
|
|
prepvec *pv;
|
|
|
|
while((pv = pv_list) != NULL) {
|
|
DELQ(pv, pv_list, prepvec *);
|
|
if (pv->pv_xpathvec)
|
|
clixon_xvec_free(pv->pv_xpathvec);
|
|
free(pv);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
prepvec *
|
|
prepvec_add(prepvec **pv_listp,
|
|
cxobj *xrule)
|
|
{
|
|
prepvec *pv;
|
|
|
|
if ((pv = malloc(sizeof(*pv))) == NULL){
|
|
clixon_err(OE_UNIX, errno, "malloc");
|
|
return NULL;
|
|
}
|
|
memset(pv, 0, sizeof(*pv));
|
|
ADDQ(pv, *pv_listp);
|
|
pv->pv_xrule = xrule;
|
|
if ((pv->pv_xpathvec = clixon_xvec_new()) == NULL)
|
|
return NULL;
|
|
return pv;
|
|
}
|
|
|
|
/*! Prepare datastructures before running through XML tree
|
|
*
|
|
* Save rules in a "cache"
|
|
* These rules match:
|
|
* - user/group
|
|
* - have read access-op, etc
|
|
* Also make instance-id lookups on top object for each rule. Assume at most one result
|
|
* @param[in] h Clixon handle
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
nacm_datanode_prepare(clixon_handle h,
|
|
cxobj *xt,
|
|
enum nacm_access access,
|
|
cxobj **gvec,
|
|
size_t glen,
|
|
cxobj **rlistvec,
|
|
size_t rlistlen,
|
|
cvec *nsc,
|
|
prepvec **pv_listp)
|
|
{
|
|
int retval = -1;
|
|
cxobj *rlist;
|
|
int i;
|
|
int j;
|
|
int k;
|
|
char *gname;
|
|
cxobj **rvec = NULL; /* rules */
|
|
size_t rlen;
|
|
cxobj *xrule;
|
|
cxobj *pathobj;
|
|
char *access_operations;
|
|
char *path0; /* Non-canonical path */
|
|
char *path=NULL; /* Canonical path */
|
|
cvec *nsc0 = NULL; /* Non-canonical namespace context */
|
|
yang_stmt *yspec;
|
|
cxobj **xvec = NULL;
|
|
int xlen = 0;
|
|
int ret;
|
|
prepvec *pv;
|
|
|
|
yspec = clicon_dbspec_yang(h);
|
|
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, nsc, ".[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, nsc, "rule", &rvec, &rlen) < 0)
|
|
goto done;
|
|
for (j=0; j<rlen; j++){ /* Loop through rules */
|
|
xrule = rvec[j];
|
|
/* 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");
|
|
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))
|
|
continue;
|
|
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"))
|
|
continue;
|
|
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"))
|
|
continue;
|
|
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"))
|
|
continue;
|
|
break;
|
|
default:
|
|
clixon_err(OE_XML, EINVAL, "Access %d unupported (shouldnt happen)", access);
|
|
goto done;
|
|
break;
|
|
}
|
|
/* 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"))
|
|
continue;
|
|
/* Here a new xrule is found, add it */
|
|
if (prepvec_add(pv_listp, xrule) == NULL)
|
|
goto done;
|
|
}
|
|
else{
|
|
path0 = clixon_trim2(xml_body(pathobj), " \t\n");
|
|
/* Get canonical namespace context for nacm paths */
|
|
if ((path = strdup(path0)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
#if 0
|
|
/* See https://github.com/clicon/clixon/issues/129:
|
|
* If this is enabled, you are back to the problem of JSON encodings
|
|
*/
|
|
/* 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;
|
|
#endif
|
|
if ((ret = clixon_xml_find_instance_id(xt, yspec, &xvec, &xlen, "%s", path)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
continue;
|
|
/* Here a new xrule is found, add it */
|
|
if ((pv = prepvec_add(pv_listp, xrule)) == NULL)
|
|
goto done;
|
|
for (k=0; k<xlen; k++){
|
|
if (clixon_xvec_append(pv->pv_xpathvec, xvec[k]) < 0)
|
|
goto done;
|
|
}
|
|
if (xvec){
|
|
free(xvec);
|
|
xvec = NULL;
|
|
}
|
|
if (nsc0){
|
|
cvec_free(nsc0);
|
|
nsc0 = NULL;
|
|
}
|
|
if (path){
|
|
free(path);
|
|
path = NULL;
|
|
}
|
|
}
|
|
}
|
|
if (rvec){
|
|
free(rvec);
|
|
rvec=NULL;
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (xvec)
|
|
free(xvec);
|
|
if (path)
|
|
free(path);
|
|
if (nsc0)
|
|
cvec_free(nsc0);
|
|
return retval;
|
|
}
|
|
|
|
/*---------------------------------------------------------------
|
|
* Datanode write
|
|
*/
|
|
|
|
/*! Match specific rule to specific requested node
|
|
*
|
|
* @param[in] xn XML node (requested node)
|
|
* @param[in] xrule NACM rule
|
|
* @param[in] xp Xpath match
|
|
* @param[in] yspec YANG spec
|
|
* @retval 2 OK and rule matches permit
|
|
* @retval 1 OK and rule matches deny
|
|
* @retval 0 OK and rule does not match
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
nacm_data_write_xrule_xml(cxobj *xn,
|
|
cxobj *xrule,
|
|
clixon_xvec *xpathvec,
|
|
yang_stmt *yspec)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ymod;
|
|
char *module_pattern; /* rule module name */
|
|
char *action;
|
|
cxobj *xp;
|
|
int i;
|
|
|
|
if ((module_pattern = 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_pattern, "*") != 0){
|
|
if (ys_module_by_xml(yspec, xn, &ymod) < 0)
|
|
goto done;
|
|
/* ymod is NULL (xn is "config") Can this breach the NACM rule? */
|
|
if (ymod && strcmp(yang_argument_get(ymod), module_pattern) != 0)
|
|
goto nomatch;
|
|
}
|
|
action = xml_find_body(xrule, "action"); /* mandatory */
|
|
/* 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 (xml_find_type(xrule, NULL, "path", CX_ELMNT) == NULL){
|
|
if (strcmp(action, "deny")==0)
|
|
goto deny;
|
|
goto permit;
|
|
}
|
|
for (i=0; i<clixon_xvec_len(xpathvec); i++){
|
|
xp = clixon_xvec_i(xpathvec, i);
|
|
/* Check if ancestor is xp (for every xpathvec?) */
|
|
if (xn == xp || xml_isancestor(xn, xp)){
|
|
if (strcmp(action, "deny")==0)
|
|
goto deny;
|
|
goto permit;
|
|
}
|
|
}
|
|
goto nomatch;
|
|
permit:
|
|
retval = 2; /* rule match and permit */
|
|
done:
|
|
return retval;
|
|
nomatch : /* rule does not match */
|
|
retval = 0;
|
|
goto done;
|
|
deny: /* rule match and deny */
|
|
retval = 1;
|
|
goto done;
|
|
}
|
|
|
|
/*! Recursive check for NACM write rules among all XML nodes
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xn XML node (requested node)
|
|
* @param[in] rulevec Precomputed rules that apply to this user group
|
|
* @param[in] xpathvec Precomputed xpath results that apply to this XML tree
|
|
* @param[in] defpermit 0 if default deny, 1 is default permit
|
|
* @param[in] yspec YANG spec
|
|
* @param[out] cbret Error message if retval = 0
|
|
* @retval 1 OK and accept
|
|
* @retval 0 Deny and cbret set
|
|
* @retval -1 Error
|
|
* XXX differentiate between nomatch: default. or match deny, match accept
|
|
* nomatch: check write-default rules, next v
|
|
* accept: Hunky dory
|
|
* deny: Send error message
|
|
*/
|
|
static int
|
|
nacm_datanode_write_recurse(clixon_handle h,
|
|
cxobj *xn,
|
|
prepvec *pv_list,
|
|
int defpermit,
|
|
yang_stmt *yspec,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
cxobj *x;
|
|
int ret = 0;
|
|
prepvec *pv;
|
|
|
|
pv = pv_list;
|
|
if (pv){
|
|
do {
|
|
/* return values: -1:Error /0:no match /1: deny /2: permit
|
|
*/
|
|
if ((ret = nacm_data_write_xrule_xml(xn, pv->pv_xrule, pv->pv_xpathvec, yspec)) < 0)
|
|
goto done;
|
|
switch(ret){
|
|
case 0: /* No match, continue with next rule */
|
|
break;
|
|
case 1: /* Match and deny: break all traversal and send error back to client */
|
|
if (netconf_access_denied(cbret, "application", "access denied") < 0)
|
|
goto done;
|
|
goto deny;
|
|
break;
|
|
case 2: /* Match and permit: break rule processing but continue recursion */
|
|
break;
|
|
}
|
|
if (ret == 2)
|
|
break;
|
|
pv = NEXTQ(prepvec *, pv);
|
|
} while (pv && pv != pv_list);
|
|
}
|
|
/* If no rule match, check default rule: if deny then break traversal and send error */
|
|
if (ret == 0 && !defpermit){
|
|
if (netconf_access_denied(cbret, "application", "default deny") < 0)
|
|
goto done;
|
|
goto deny;
|
|
}
|
|
/* If node should be purged, dont recurse and defer removal to caller */
|
|
x = NULL; /* Recursively check XML */
|
|
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
|
|
if ((ret = nacm_datanode_write_recurse(h, x, pv_list,
|
|
defpermit, yspec, cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto deny;
|
|
}
|
|
retval = 1; /* accept */
|
|
done:
|
|
return retval;
|
|
deny:
|
|
retval = 0; /* deny */
|
|
goto done;
|
|
}
|
|
|
|
/*! 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] h Clixon handle
|
|
* @param[in] xreq XML requestor node (part of xt) for delete it is existing, for others it is new
|
|
* @param[in] xt XML request root tree with "config" label at top.
|
|
* @param[in] op NACM access of xreq
|
|
* @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.
|
|
* @retval 1 Access
|
|
* @retval 0 Not access and cbret set
|
|
* @retval -1 Error
|
|
* @see RFC8341 3.4.5. Data Node Access Validation
|
|
* @see nacm_datanode_read
|
|
* @see nacm_rpc
|
|
*/
|
|
int
|
|
nacm_datanode_write(clixon_handle h,
|
|
cxobj *xreq,
|
|
cxobj *xt,
|
|
enum nacm_access access,
|
|
char *username,
|
|
cxobj *xnacm,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
cxobj **gvec = NULL; /* groups */
|
|
size_t glen;
|
|
cxobj **rlistvec = NULL; /* rule-list */
|
|
size_t rlistlen;
|
|
cxobj **rvec = NULL; /* rules */
|
|
char *write_default = NULL;
|
|
cvec *nsc = NULL;
|
|
int ret;
|
|
prepvec *pv_list = NULL;
|
|
|
|
/* Create namespace context for with nacm namespace as default */
|
|
if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL)
|
|
goto done;
|
|
if (xnacm == NULL)
|
|
goto permit;
|
|
/* write-default (create, update, or delete) has default deny so should never be NULL */
|
|
if ((write_default = xml_find_body(xnacm, "write-default")) == NULL){
|
|
clixon_err(OE_XML, EINVAL, "No nacm write-default rule");
|
|
goto done;
|
|
}
|
|
/* 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, nsc, "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, nsc, "rule-list", &rlistvec, &rlistlen) < 0)
|
|
goto done;
|
|
/* First run through rules and cache rules as well as lookup objects in xt.
|
|
*/
|
|
if (nacm_datanode_prepare(h, xt, access, gvec, glen, rlistvec, rlistlen, nsc, &pv_list) < 0)
|
|
goto done;
|
|
/* Then recursivelyy traverse all requested nodes */
|
|
if ((ret = nacm_datanode_write_recurse(h, xreq, pv_list,
|
|
strcmp(write_default, "deny"),
|
|
clicon_dbspec_yang(h),
|
|
cbret)) < 0)
|
|
goto done;
|
|
if (ret == 0) /* deny */
|
|
goto deny;
|
|
goto permit;
|
|
/* 8. At this point, no matching rule was found in any rule-list
|
|
entry. */
|
|
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 has default permit so should never be NULL */
|
|
if (strcmp(write_default, "deny") == 0){
|
|
if (netconf_access_denied(cbret, "application", "default deny") < 0)
|
|
goto done;
|
|
goto deny;
|
|
}
|
|
permit:
|
|
retval = 1;
|
|
done:
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s retval:%d (0:deny 1:permit)", __FUNCTION__, retval);
|
|
if (pv_list)
|
|
prepvec_free(pv_list);
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
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;
|
|
}
|
|
|
|
/*---------------------------------------------------------------
|
|
* Datanode read
|
|
*/
|
|
|
|
/*! Perform NACM action: mark if permit, del if deny
|
|
*
|
|
* @param[in] xrule NACM rule
|
|
* @param[in] xn XML node (requested node)
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
|
|
*/
|
|
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] xn XML node (requested node)
|
|
* @param[in] xrule NACM rule
|
|
* @param[in] yspec YANG spec
|
|
* @retval 1 OK and rule matches
|
|
* @retval 0 OK and rule does not match
|
|
* @retval -1 Error
|
|
* 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 *xn,
|
|
cxobj *xrule,
|
|
clixon_xvec *xpathvec,
|
|
yang_stmt *yspec)
|
|
{
|
|
int retval = -1;
|
|
yang_stmt *ymod;
|
|
char *module_pattern; /* rule module name */
|
|
cxobj *xp;
|
|
int i;
|
|
|
|
if ((module_pattern = 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_pattern, "*") != 0){
|
|
if (ys_module_by_xml(yspec, xn, &ymod) < 0)
|
|
goto done;
|
|
if (strcmp(yang_argument_get(ymod), module_pattern) != 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 (xml_find_type(xrule, NULL, "path", CX_ELMNT) == NULL){
|
|
if (nacm_data_read_action(xrule, xn) < 0)
|
|
goto done;
|
|
goto match;
|
|
}
|
|
for (i=0; i<clixon_xvec_len(xpathvec); i++){
|
|
xp = clixon_xvec_i(xpathvec, i);
|
|
/* Check if ancestor is xp (for every xpathvec?) */
|
|
if (xn == xp || xml_isancestor(xn, xp)){
|
|
if (nacm_data_read_action(xrule, xn) < 0)
|
|
goto done;
|
|
goto match;
|
|
}
|
|
}
|
|
nomatch:
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
match:
|
|
retval = 1; /* match */
|
|
goto done;
|
|
}
|
|
|
|
/*! Recursive check for NACM read rules among all XML nodes
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] xn XML node (requested node)
|
|
* @param[in] rulevec Precomputed rules that apply to this user group
|
|
* @param[in] xpathvec Precomputed xpath results that apply to this XML tree
|
|
* @param[in] yspec YANG spec
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
nacm_datanode_read_recurse(clixon_handle h,
|
|
cxobj *xn,
|
|
prepvec *pv_list,
|
|
yang_stmt *yspec)
|
|
{
|
|
int retval = -1;
|
|
cxobj *x;
|
|
cxobj *xprev;
|
|
int ret;
|
|
prepvec *pv;
|
|
|
|
if (xml_spec(xn)){ /* Check this node */
|
|
pv = pv_list;
|
|
if (pv){
|
|
do {
|
|
if ((ret = nacm_data_read_xrule_xml(xn,
|
|
pv->pv_xrule,
|
|
pv->pv_xpathvec,
|
|
yspec)) < 0)
|
|
goto done;
|
|
if (ret == 1)
|
|
break; /* stop at first match */
|
|
pv = NEXTQ(prepvec *, pv);
|
|
} while (pv && pv != pv_list);
|
|
}
|
|
|
|
#if 0 /* 6(A) in algorithm
|
|
* If N did not match any rule R, and default rule is deny, remove that subtree */
|
|
if (strcmp(read_default, "deny") == 0)
|
|
if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
|
|
goto done;
|
|
#endif
|
|
}
|
|
|
|
/* If node should be purged, dont recurse and defer removal to caller */
|
|
if (xml_flag(xn, XML_FLAG_DEL) == 0){
|
|
x = NULL; /* Recursively check XML */
|
|
xprev = NULL;
|
|
while ((x = xml_child_each(xn, x, CX_ELMNT)) != NULL) {
|
|
if (nacm_datanode_read_recurse(h, x, pv_list, yspec) < 0)
|
|
goto done;
|
|
/* check for delayed remove */
|
|
if (xml_flag(x, XML_FLAG_DEL)){
|
|
if (xml_purge(x) < 0)
|
|
goto done;
|
|
x = xprev;
|
|
}
|
|
}
|
|
}
|
|
retval = 0;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Make nacm datanode and module rule read access validation
|
|
*
|
|
* Just purge nodes that fail validation (dont send netconf error message)
|
|
* @param[in] h Clixon handle
|
|
* @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 Access
|
|
* @retval 0 Not access and cbret set
|
|
* @retval -1 Error
|
|
* 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
|
|
* @note 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
|
|
*
|
|
* Some observations:
|
|
* 1. The requested node is a set of nodes in a tree (not just the top-node)
|
|
* 2. Any node descendants of a deny is denied (except default)
|
|
* 3. First rule matching a node is the active rule
|
|
*
|
|
* Algorithm: Select either (A) or (B)
|
|
*
|
|
* 1. Select next node N in the requested node tree:
|
|
* 2. Select next R rule in the set of applicable rules:
|
|
* 3. If N does not match R, and remaining rules, goto 2.
|
|
* 4. If N matches R as deny, remove that subtree
|
|
* 5. If N matches R as accept, mark that node
|
|
* 6(A). If N did not match any rule R, and default rule is deny, remove that subtree
|
|
* 7. If remaining nodes, goto 1
|
|
* 8(B) If default rule is deny, recursively remove all subtrees that are not marked
|
|
*
|
|
* @see RFC8341 3.4.5. Data Node Access Validation
|
|
* @see nacm_datanode_write
|
|
* @see nacm_rpc
|
|
*/
|
|
int
|
|
nacm_datanode_read(clixon_handle h,
|
|
cxobj *xt,
|
|
cxobj **xrvec,
|
|
size_t xrlen,
|
|
char *username,
|
|
cxobj *xnacm)
|
|
{
|
|
int retval = -1;
|
|
cxobj **gvec = NULL; /* groups */
|
|
size_t glen;
|
|
cxobj **rlistvec = NULL; /* rule-list */
|
|
size_t rlistlen;
|
|
int i;
|
|
char *read_default = NULL;
|
|
cvec *nsc = NULL;
|
|
prepvec *pv_list = NULL;
|
|
|
|
/* Create namespace context for with nacm namespace as default */
|
|
if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL)
|
|
goto done;
|
|
/* 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, nsc, "groups/group[user-name='%s']", &gvec, &glen, username) < 0)
|
|
goto done;
|
|
/* 4. If no groups are found (glen=0), continue and check read-default
|
|
in step 11. */
|
|
/* 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, nsc, "rule-list", &rlistvec, &rlistlen) < 0)
|
|
goto done;
|
|
/* read-default has default permit so should never be NULL */
|
|
if ((read_default = xml_find_body(xnacm, "read-default")) == NULL){
|
|
clixon_err(OE_XML, EINVAL, "No nacm read-default rule");
|
|
goto done;
|
|
}
|
|
/* First run through rules and cache rules as well as lookup objects in xt.
|
|
* DANGER: objects could be stale if they are removed?
|
|
*/
|
|
if (nacm_datanode_prepare(h, xt, NACM_READ, gvec, glen, rlistvec, rlistlen, nsc, &pv_list) < 0)
|
|
goto done;
|
|
/* Then recursivelyy traverse all nodes */
|
|
if (nacm_datanode_read_recurse(h, xt, pv_list, clicon_dbspec_yang(h)) < 0)
|
|
goto done;
|
|
#if 1
|
|
/* Step 8(B) above:
|
|
* If default rule is deny, recursively remove all subtrees that are not marked
|
|
*/
|
|
if (strcmp(read_default, "deny") == 0)
|
|
if (xml_tree_prune_flagged_sub(xt, XML_FLAG_MARK, 1, NULL) < 0)
|
|
goto done;
|
|
#endif
|
|
/* reset flag */
|
|
if (xml_apply(xt, CX_ELMNT, (xml_applyfn_t*)xml_flag_reset, (void*)XML_FLAG_MARK) < 0)
|
|
goto done;
|
|
|
|
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, safe since vector not children */
|
|
if (xml_purge(xrvec[i]) < 0)
|
|
goto done;
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s retval:%d", __FUNCTION__, retval);
|
|
if (pv_list)
|
|
prepvec_free(pv_list);
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
if (gvec)
|
|
free(gvec);
|
|
if (rlistvec)
|
|
free(rlistvec);
|
|
return retval;
|
|
}
|
|
|
|
|
|
/*---------------------------------------------------------------
|
|
* 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 Clixon handle
|
|
* @param[in] xnacm NACM XML tree, root should be "nacm"
|
|
* @param[in] username User name of requestor
|
|
* @retval 1 OK permitted. You do not need to do next NACM step
|
|
* @retval 0 OK but not validated. Need to do NACM step using xnacm
|
|
* @retval -1 Error
|
|
* @code
|
|
* if ((ret = nacm_access_check(h, mode, xnacm, peername, username)) < 0)
|
|
* err;
|
|
* if (ret == 0){
|
|
* // Next step NACM processing
|
|
* xml_free(xnacm);
|
|
* }
|
|
* @endcode
|
|
* @see RFC8341 3.4 Access Control Enforcement Procedures
|
|
*/
|
|
static int
|
|
nacm_access_check(clixon_handle h,
|
|
cxobj *xnacm,
|
|
char *peername,
|
|
char *username)
|
|
{
|
|
int retval = -1;
|
|
char *enabled;
|
|
cxobj *x;
|
|
cvec *nsc = NULL;
|
|
char *recovery_user;
|
|
#ifdef WITH_RESTCONF
|
|
char *wwwuser;
|
|
#endif
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
|
|
if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL)
|
|
goto done;
|
|
/* 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.
|
|
* note option CLICON_NACM_DISABLED_ON_EMPTY
|
|
*/
|
|
if ((x = xpath_first(xnacm, nsc, "enable-nacm")) == NULL)
|
|
goto permit;
|
|
enabled = xml_body(x);
|
|
if (strcmp(enabled, "true") != 0)
|
|
goto permit;
|
|
recovery_user=clicon_nacm_recovery_user(h);
|
|
/* 2. If the requesting session is identified as a recovery session,
|
|
* then the protocol operation is permitted.
|
|
*/
|
|
if (username && peername && recovery_user &&
|
|
strcmp(username, recovery_user) == 0){
|
|
/* Recovery session in clixon is defined as
|
|
* 1) username (sent in message) is recovery user
|
|
* AND
|
|
* if cred is EXACT:
|
|
* 2a) peername is also recovery user
|
|
* if cred is EXCEPT/NONE:;
|
|
* 2b) peername is recovery user/root/WWWUSER
|
|
*/
|
|
if (strcmp(peername, recovery_user) == 0)
|
|
goto permit;
|
|
switch(clicon_nacm_credentials(h)){
|
|
case NC_EXACT:
|
|
break;
|
|
case NC_NONE:
|
|
goto permit;
|
|
break;
|
|
case NC_EXCEPT:
|
|
if (strcmp(username, recovery_user) == 0 &&
|
|
strcmp(peername, "root") == 0)
|
|
goto permit;
|
|
#ifdef WITH_RESTCONF
|
|
wwwuser=clicon_option_str(h,"CLICON_RESTCONF_USER");
|
|
if (strcmp(username, recovery_user) == 0 &&
|
|
wwwuser && strcmp(peername, wwwuser) == 0)
|
|
goto permit;
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
retval = 0; /* not permitted yet. continue with next NACM step */
|
|
done:
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%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 Clixon handle
|
|
* @param[in] username User name of requestor
|
|
* @param[out] xncam NACM XML tree, set if retval=0. Free after use
|
|
* @retval 1 OK permitted. You do not need to do next NACM step.
|
|
* @retval 0 OK but not validated. Need to do NACM step using xnacm
|
|
* @retval -1 Error
|
|
* @code
|
|
* cxobj *xnacm = NULL;
|
|
* if ((ret = nacm_access_pre(h, peername, 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(clixon_handle h,
|
|
char *peername,
|
|
char *username,
|
|
cxobj **xnacmp)
|
|
{
|
|
int retval = -1;
|
|
char *mode;
|
|
cxobj *x;
|
|
cxobj *xnacm0 = NULL;
|
|
cxobj *xnacm = NULL;
|
|
cvec *nsc = NULL;
|
|
|
|
/* Check clixon option: disabled, external tree or internal */
|
|
mode = clicon_option_str(h, "CLICON_NACM_MODE");
|
|
if (mode == NULL)
|
|
goto permit;
|
|
else if (strcmp(mode, "disabled")==0)
|
|
goto permit;
|
|
else 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_get0(h, "running", YB_MODULE, nsc, "nacm", 1, 0, &xnacm0, NULL, NULL) < 0)
|
|
goto done;
|
|
}
|
|
else{
|
|
clixon_err(OE_XML, 0, "Invalid NACM mode: %s", mode);
|
|
goto done;
|
|
}
|
|
if ((nsc = xml_nsctx_init(NULL, NACM_NS)) == NULL)
|
|
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, nsc, "nacm")) == NULL)
|
|
goto permit;
|
|
if (xml_rootchild_node(xnacm0, xnacm) < 0)
|
|
goto done;
|
|
xnacm0 = NULL;
|
|
/* Initial NACM steps and common to all NACM access validation. */
|
|
if ((retval = nacm_access_check(h, xnacm, peername, username)) < 0)
|
|
goto done;
|
|
if (retval == 0){ /* if retval == 0 then return an xml nacm tree */
|
|
*xnacmp = xnacm;
|
|
xnacm = NULL;
|
|
}
|
|
done:
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
if (xnacm0)
|
|
xml_free(xnacm0);
|
|
else if (xnacm)
|
|
xml_free(xnacm);
|
|
return retval;
|
|
permit:
|
|
retval = 1;
|
|
goto done;
|
|
}
|
|
|
|
/*! Verify nacm user with peer uid credentials
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] mode Peer credential mode: none, exact or except
|
|
* @param[in] peername Peer username if any
|
|
* @param[in] username username received in XML (eg for NACM)
|
|
* @param[out] cbret Set with netconf error message if ret == 0
|
|
* @retval 1 Verified
|
|
* @retval 0 Not verified (cbret set)
|
|
* @retval -1 Error
|
|
* Credentials OK if
|
|
* - cred mode is NONE,
|
|
* Otherwise both NACM user AND peer user must exist, and
|
|
* if cred mode is EXACT and
|
|
* - peer user is same as NACM user
|
|
* or if cred mode is EXCEPT and one of the following is true
|
|
* - peer user is same as NACM user
|
|
* - peer user is root (can be any NACM user)
|
|
* - peer user is www (can be any NACM user)
|
|
*/
|
|
int
|
|
verify_nacm_user(clixon_handle h,
|
|
enum nacm_credentials_t cred,
|
|
char *peername,
|
|
char *nacmname,
|
|
cbuf *cbret)
|
|
{
|
|
int retval = -1;
|
|
cbuf *cbmsg = NULL;
|
|
#ifdef WITH_RESTCONF
|
|
char *wwwuser;
|
|
#endif
|
|
|
|
if (cred == NC_NONE)
|
|
return 1;
|
|
if (peername == NULL){
|
|
if (netconf_access_denied(cbret, "application", "No peer user credentials available") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
if (nacmname == NULL){
|
|
if (netconf_access_denied(cbret, "application", "No NACM available") < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
if (cred == NC_EXCEPT){
|
|
if (strcmp(peername, "root") == 0)
|
|
goto ok;
|
|
#ifdef WITH_RESTCONF
|
|
wwwuser=clicon_option_str(h,"CLICON_RESTCONF_USER");
|
|
if (wwwuser && strcmp(peername, wwwuser) == 0)
|
|
goto ok;
|
|
#endif
|
|
}
|
|
if (strcmp(peername, nacmname) != 0){
|
|
if ((cbmsg = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
cprintf(cbmsg, "User %s credential not matching NACM user %s", peername, nacmname);
|
|
if (netconf_access_denied(cbret, "application", cbuf_get(cbmsg)) < 0)
|
|
goto done;
|
|
goto fail;
|
|
}
|
|
ok:
|
|
retval = 1;
|
|
done:
|
|
if (cbmsg)
|
|
cbuf_free(cbmsg);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|