* Added a "user" parameter to plugin_credentials() restconf callback.

To enable authentication and in preparation for access control a la RFC 6536.
* yang string length "max" keyword set to MAXPATHLEN
This commit is contained in:
Olof hagsand 2018-02-08 15:24:05 +07:00
parent 3ffe68d124
commit e40d785d5c
12 changed files with 514 additions and 34 deletions

View file

@ -3,6 +3,7 @@
## 3.5.0 (Upcoming) ## 3.5.0 (Upcoming)
### Major changes: ### Major changes:
* Added a "user" parameter to plugin_credentials() restconf callback. To enable authentication and in preparation for access control a la RFC 6536.
* Major Restconf feature update to compy to RFC 8040. Thanks Stephen Jones for getting right. * Major Restconf feature update to compy to RFC 8040. Thanks Stephen Jones for getting right.
* GET well-known, top-level resource, yang library version, * GET well-known, top-level resource, yang library version,
* PUT whole datastore, check for different keys in put lists. * PUT whole datastore, check for different keys in put lists.
@ -10,7 +11,7 @@
### Minor changes: ### Minor changes:
* Added RFC 6536 ietf-netconf-acm@2012-02-22.yang access control (but not implemented).
* The following backward compatible options to configure have been obsoleted. If you havent already migrated this code you must do this now. * The following backward compatible options to configure have been obsoleted. If you havent already migrated this code you must do this now.
* Backend startup modes prior to 3.3.3. As enabled with `configure --with-startup-compat`. Configure option CLICON_USE_STARTUP_CONFIG is also obsoleted. * Backend startup modes prior to 3.3.3. As enabled with `configure --with-startup-compat`. Configure option CLICON_USE_STARTUP_CONFIG is also obsoleted.
* Configuration files (non-XML) prior to 3.3.3. As enabled with `configure --with-config-compat`. The template clicon.conf.cpp files are also removed. * Configuration files (non-XML) prior to 3.3.3. As enabled with `configure --with-config-compat`. The template clicon.conf.cpp files are also removed.
@ -25,6 +26,7 @@
* /etc/clixon.xml * /etc/clixon.xml
### Corrected Bugs ### Corrected Bugs
* yang string length "max" keyword set to MAXPATHLEN
* Corrected "No yang spec" printed on tty on leafref CLI usage * Corrected "No yang spec" printed on tty on leafref CLI usage
* xml2cvec: range error (eg 1000 for int8) is not treated as error, just log and skip. * xml2cvec: range error (eg 1000 for int8) is not treated as error, just log and skip.

View file

@ -238,12 +238,21 @@ yang2cli_var_sub(clicon_handle h,
goto done; goto done;
} }
} }
else{ /* Cligen does not have 'max' keyword in range so need to find actual else{ /* Cligen does not have 'max' keyword in range so need to find actual
max value of type if yang range expression is 0..max */ max value of type if yang range expression is 0..max
if ((r = cvtype_max2str_dup(cvtype)) == NULL){ */
clicon_err(OE_UNIX, errno, "cvtype_max2str"); if (cvtype==CGV_STRING){
goto done; if ((r = malloc(512)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
snprintf(r, 512, "%d", MAXPATHLEN);
} }
else if ((r = cvtype_max2str_dup(cvtype)) == NULL){
clicon_err(OE_UNIX, errno, "cvtype_max2str");
goto done;
}
} }
cprintf(cb, "%s]", r); cprintf(cb, "%s]", r);
free(r); free(r);

View file

@ -79,7 +79,7 @@
* Returns an expand-type list of commands as used by cligen 'expand' * Returns an expand-type list of commands as used by cligen 'expand'
* functionality. * functionality.
* *
* Assume callback given in a cligen spec: a <x:int expand_dbvar("arg") * Assume callback given in a cligen spec: a <x:int expand_dbvar("db" "<xmlkeyfmt>")
* @param[in] h clicon handle * @param[in] h clicon handle
* @param[in] name Name of this function (eg "expand_dbvar") * @param[in] name Name of this function (eg "expand_dbvar")
* @param[in] cvv The command so far. Eg: cvec [0]:"a 5 b"; [1]: x=5; * @param[in] cvv The command so far. Eg: cvec [0]:"a 5 b"; [1]: x=5;

View file

@ -296,11 +296,10 @@ readdata(FCGX_Request *r)
return cb; return cb;
} }
typedef int (credentials_t)(clicon_handle h, FCGX_Request *r);
static int nplugins = 0; static int nplugins = 0;
static plghndl_t *plugins = NULL; static plghndl_t *plugins = NULL;
static credentials_t *p_credentials = NULL; /* Credentials callback */ static plgcredentials_t *_credentials_fn = NULL; /* Credentials callback */
/*! Load all plugins you can find in CLICON_RESTCONF_DIR /*! Load all plugins you can find in CLICON_RESTCONF_DIR
*/ */
@ -330,7 +329,7 @@ restconf_plugin_load(clicon_handle h)
(int)strlen(filename), filename); (int)strlen(filename), filename);
if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL) if ((handle = plugin_load(h, filename, RTLD_NOW)) == NULL)
goto quit; goto quit;
p_credentials = dlsym(handle, PLUGIN_CREDENTIALS); _credentials_fn = dlsym(handle, PLUGIN_CREDENTIALS);
if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) { if ((plugins = realloc(plugins, (nplugins+1) * sizeof (*plugins))) == NULL) {
clicon_err(OE_UNIX, errno, "realloc"); clicon_err(OE_UNIX, errno, "realloc");
goto quit; goto quit;
@ -385,23 +384,24 @@ restconf_plugin_start(clicon_handle h,
} }
int int
plugin_credentials(clicon_handle h, restconf_credentials(clicon_handle h,
FCGX_Request *r, FCGX_Request *r,
int *auth) char **user)
{ {
int retval = -1; int retval = -1;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
/* If no authentication callback then allow anything. Is this OK? */ /* If no authentication callback then allow anything. Is this OK? */
if (p_credentials == 0){ if (_credentials_fn == NULL){
*auth = 1; if ((*user = strdup("none")) == NULL){
clicon_err(OE_XML, errno, "strdup");
goto done;
}
retval = 0; retval = 0;
goto done; goto done;
} }
if (p_credentials(h, r) < 0) if (_credentials_fn(h, r, user) < 0)
*auth = 0; user = NULL;
else
*auth = 1;
retval = 0; retval = 0;
done: done:
return retval; return retval;

View file

@ -56,7 +56,7 @@ cbuf *readdata(FCGX_Request *r);
int restconf_plugin_load(clicon_handle h); int restconf_plugin_load(clicon_handle h);
int restconf_plugin_start(clicon_handle h, int argc, char **argv); int restconf_plugin_start(clicon_handle h, int argc, char **argv);
int restconf_plugin_unload(clicon_handle h); int restconf_plugin_unload(clicon_handle h);
int plugin_credentials(clicon_handle h, FCGX_Request *r, int *auth); int restconf_credentials(clicon_handle h, FCGX_Request *r, char **user);
int get_user_cookie(char *cookiestr, char *attribute, char **val); int get_user_cookie(char *cookiestr, char *attribute, char **val);

View file

@ -142,7 +142,8 @@ api_operations(clicon_handle h,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
cvec *qvec, cvec *qvec,
char *data) char *data,
char *username)
{ {
int retval = -1; int retval = -1;
char *request_method; char *request_method;
@ -153,7 +154,7 @@ api_operations(clicon_handle h,
if (strcmp(request_method, "GET")==0) if (strcmp(request_method, "GET")==0)
retval = api_operation_get(h, r, path, pcvec, pi, qvec, data); retval = api_operation_get(h, r, path, pcvec, pi, qvec, data);
else if (strcmp(request_method, "POST")==0) else if (strcmp(request_method, "POST")==0)
retval = api_operation_post(h, r, path, pcvec, pi, qvec, data); retval = api_operation_post(h, r, path, pcvec, pi, qvec, data, username);
else else
retval = notfound(r); retval = notfound(r);
return retval; return retval;
@ -275,7 +276,7 @@ api_restconf(clicon_handle h,
cvec *pcvec = NULL; /* for rest api */ cvec *pcvec = NULL; /* for rest api */
cbuf *cb = NULL; cbuf *cb = NULL;
char *data; char *data;
int auth = 0; char *username = NULL;
clicon_debug(1, "%s", __FUNCTION__); clicon_debug(1, "%s", __FUNCTION__);
path = FCGX_GetParam("REQUEST_URI", r->envp); path = FCGX_GetParam("REQUEST_URI", r->envp);
@ -318,20 +319,21 @@ api_restconf(clicon_handle h,
retval = 0; retval = 0;
test(r, 1); test(r, 1);
/* If present, check credentials */ /* If present, check credentials. See "plugin_credentials" in plugin
if (plugin_credentials(h, r, &auth) < 0) * See RFC 8040 section 2.5
*/
if (restconf_credentials(h, r, &username) < 0)
goto done; goto done;
clicon_debug(1, "%s credentials ok auth:%d (should be 1)", clicon_debug(1, "%s credentials ok username:%s (should be non-NULL)",
__FUNCTION__, auth); __FUNCTION__, username);
if (auth == 0) if (username == NULL)
goto done; goto done;
clicon_debug(1, "%s credentials ok 2", __FUNCTION__);
if (strcmp(method, "yang-library-version")==0) if (strcmp(method, "yang-library-version")==0)
retval = api_yang_library_version(h, r); retval = api_yang_library_version(h, r);
else if (strcmp(method, "data") == 0) /* restconf, skip /api/data */ else if (strcmp(method, "data") == 0) /* restconf, skip /api/data */
retval = api_data(h, r, path, pcvec, 2, qvec, data); retval = api_data(h, r, path, pcvec, 2, qvec, data);
else if (strcmp(method, "operations") == 0) /* rpc */ else if (strcmp(method, "operations") == 0) /* rpc */
retval = api_operations(h, r, path, pcvec, 2, qvec, data); retval = api_operations(h, r, path, pcvec, 2, qvec, data, username);
else if (strcmp(method, "test") == 0) else if (strcmp(method, "test") == 0)
retval = test(r, 0); retval = test(r, 0);
else else
@ -348,6 +350,8 @@ api_restconf(clicon_handle h,
cvec_free(pcvec); cvec_free(pcvec);
if (cb) if (cb)
cbuf_free(cb); cbuf_free(cb);
if (username)
free(username);
return retval; return retval;
} }

View file

@ -846,7 +846,8 @@ api_operation_post(clicon_handle h,
cvec *pcvec, cvec *pcvec,
int pi, int pi,
cvec *qvec, cvec *qvec,
char *data) char *data,
char *username)
{ {
int retval = -1; int retval = -1;
int i; int i;
@ -869,6 +870,7 @@ api_operation_post(clicon_handle h,
char *media_accept; char *media_accept;
int use_xml = 0; /* By default return JSON */ int use_xml = 0; /* By default return JSON */
int pretty; int pretty;
cxobj *xa;
clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path); clicon_debug(1, "%s json:\"%s\" path:\"%s\"", __FUNCTION__, data, path);
pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY"); pretty = clicon_option_bool(h, "CLICON_RESTCONF_PRETTY");
@ -939,8 +941,17 @@ api_operation_post(clicon_handle h,
} }
} }
} }
/* Non-standard: add cookie as attribute for backend /* Non-standard: add username attribute for backend ACM (RFC 6536)
*
*/ */
if (username){
if ((xa = xml_new("username", xtop, NULL)) == NULL)
goto done;
xml_type_set(xa, CX_ATTR);
if (xml_value_set(xa, username) < 0)
goto done;
}
#ifdef obsolete
{ {
cxobj *xa; cxobj *xa;
char *cookie; char *cookie;
@ -957,6 +968,7 @@ api_operation_post(clicon_handle h,
free(cookieval); free(cookieval);
} }
} }
#endif
/* Send to backend */ /* Send to backend */
if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0) if (clicon_rpc_netconf_xml(h, xtop, &xret, NULL) < 0)
goto done; goto done;

View file

@ -66,6 +66,7 @@ int api_operation_get(clicon_handle h, FCGX_Request *r,
int api_operation_post(clicon_handle h, FCGX_Request *r, int api_operation_post(clicon_handle h, FCGX_Request *r,
char *path, char *path,
cvec *pcvec, int pi, cvec *qvec, char *data); cvec *pcvec, int pi, cvec *qvec, char *data,
char *username);
#endif /* _RESTCONF_METHODS_H_ */ #endif /* _RESTCONF_METHODS_H_ */

View file

@ -77,7 +77,10 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
* Returns 0 if credentials OK, -1 if failed * Returns 0 if credentials OK, -1 if failed
*/ */
#define PLUGIN_CREDENTIALS "plugin_credentials" #define PLUGIN_CREDENTIALS "plugin_credentials"
typedef int (plgcredentials_t)(clicon_handle, void *); /* Plugin credentials */ /* Plugin credentials
* username should be freed after use
*/
typedef int (plgcredentials_t)(clicon_handle, void *, char **username);
/* Find a function in global namespace or a plugin. XXX clicon internal */ /* Find a function in global namespace or a plugin. XXX clicon internal */
void *clicon_find_func(clicon_handle h, char *plugin, char *func); void *clicon_find_func(clicon_handle h, char *plugin, char *func);

View file

@ -1704,6 +1704,7 @@ xml_apply_ancestor(cxobj *xn,
* @param[out] cvp CLIgen variable containing the parsed value * @param[out] cvp CLIgen variable containing the parsed value
* @note free cv with cv_free after use. * @note free cv with cv_free after use.
* @see xml_body_int32 etc, for type-specific parse functions * @see xml_body_int32 etc, for type-specific parse functions
* @note range check failure returns 0
*/ */
int int
xml_body_parse(cxobj *xb, xml_body_parse(cxobj *xb,
@ -1751,6 +1752,7 @@ xml_body_parse(cxobj *xb,
* alloc error. * alloc error.
* @note extend to all other cligen var types and generalize * @note extend to all other cligen var types and generalize
* @note use yang type info? * @note use yang type info?
* @note range check failure returns 0
*/ */
int int
xml_body_int32(cxobj *xb, xml_body_int32(cxobj *xb,
@ -1774,6 +1776,7 @@ xml_body_int32(cxobj *xb,
* alloc error. * alloc error.
* @note extend to all other cligen var types and generalize * @note extend to all other cligen var types and generalize
* @note use yang type info? * @note use yang type info?
* @note range check failure returns 0
*/ */
int int
xml_body_uint32(cxobj *xb, xml_body_uint32(cxobj *xb,

View file

@ -40,6 +40,7 @@ datarootdir = @datarootdir@
YANGSPECS = clixon-config@2017-12-27.yang YANGSPECS = clixon-config@2017-12-27.yang
YANGSPECS += ietf-netconf@2011-06-01.yang YANGSPECS += ietf-netconf@2011-06-01.yang
YANGSPECS += ietf-netconf-acm@2012-02-22.yang
YANGSPECS += ietf-inet-types@2013-07-15.yang YANGSPECS += ietf-inet-types@2013-07-15.yang
APPNAME = clixon # subdir ehere these files are installed APPNAME = clixon # subdir ehere these files are installed

View file

@ -0,0 +1,445 @@
module ietf-netconf-acm {
namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm";
prefix "nacm";
import ietf-yang-types {
prefix yang;
}
organization
"IETF NETCONF (Network Configuration) Working Group";
contact
"WG Web: <http://tools.ietf.org/wg/netconf/>
WG List: <mailto:netconf@ietf.org>
WG Chair: Mehmet Ersue
<mailto:mehmet.ersue@nsn.com>
WG Chair: Bert Wijnen
<mailto:bertietf@bwijnen.net>
Editor: Andy Bierman
<mailto:andy@yumaworks.com>
Editor: Martin Bjorklund
<mailto:mbj@tail-f.com>";
description
"NETCONF Access Control Model.
Copyright (c) 2012 IETF Trust and the persons identified as
authors of the code. All rights reserved.
Redistribution and use in source and binary forms, with or
without modification, is permitted pursuant to, and subject
to the license terms contained in, the Simplified BSD
License set forth in Section 4.c of the IETF Trust's
Legal Provisions Relating to IETF Documents
(http://trustee.ietf.org/license-info).
This version of this YANG module is part of RFC 6536; see
the RFC itself for full legal notices.";
revision "2012-02-22" {
description
"Initial version";
reference
"RFC 6536: Network Configuration Protocol (NETCONF)
Access Control Model";
}
/*
* Extension statements
*/
extension default-deny-write {
description
"Used to indicate that the data model node
represents a sensitive security system parameter.
If present, and the NACM module is enabled (i.e.,
/nacm/enable-nacm object equals 'true'), the NETCONF server
will only allow the designated 'recovery session' to have
write access to the node. An explicit access control rule is
required for all other users.
The 'default-deny-write' extension MAY appear within a data
definition statement. It is ignored otherwise.";
}
extension default-deny-all {
description
"Used to indicate that the data model node
controls a very sensitive security system parameter.
If present, and the NACM module is enabled (i.e.,
/nacm/enable-nacm object equals 'true'), the NETCONF server
will only allow the designated 'recovery session' to have
read, write, or execute access to the node. An explicit
access control rule is required for all other users.
The 'default-deny-all' extension MAY appear within a data
definition statement, 'rpc' statement, or 'notification'
statement. It is ignored otherwise.";
}
/*
* Derived types
*/
typedef user-name-type {
type string {
length "1..max";
}
description
"General Purpose Username string.";
}
typedef matchall-string-type {
type string {
pattern "\*";
}
description
"The string containing a single asterisk '*' is used
to conceptually represent all possible values
for the particular leaf using this data type.";
}
typedef access-operations-type {
type bits {
bit create {
description
"Any protocol operation that creates a
new data node.";
}
bit read {
description
"Any protocol operation or notification that
returns the value of a data node.";
}
bit update {
description
"Any protocol operation that alters an existing
data node.";
}
bit delete {
description
"Any protocol operation that removes a data node.";
}
bit exec {
description
"Execution access to the specified protocol operation.";
}
}
description
"NETCONF Access Operation.";
}
typedef group-name-type {
type string {
length "1..max";
pattern "[^\*].*";
}
description
"Name of administrative group to which
users can be assigned.";
}
typedef action-type {
type enumeration {
enum permit {
description
"Requested action is permitted.";
}
enum deny {
description
"Requested action is denied.";
}
}
description
"Action taken by the server when a particular
rule matches.";
}
typedef node-instance-identifier {
type yang:xpath1.0;
description
"Path expression used to represent a special
data node instance identifier string.
A node-instance-identifier value is an
unrestricted YANG instance-identifier expression.
All the same rules as an instance-identifier apply
except predicates for keys are optional. If a key
predicate is missing, then the node-instance-identifier
represents all possible server instances for that key.
This XPath expression is evaluated in the following context:
o The set of namespace declarations are those in scope on
the leaf element where this type is used.
o The set of variable bindings contains one variable,
'USER', which contains the name of the user of the current
session.
o The function library is the core function library, but
note that due to the syntax restrictions of an
instance-identifier, no functions are allowed.
o The context node is the root node in the data tree.";
}
/*
* Data definition statements
*/
container nacm {
/* nacm:default-deny-all; XXX How is this parsed ?? */
description
"Parameters for NETCONF Access Control Model.";
leaf enable-nacm {
type boolean;
default true;
description
"Enables or disables all NETCONF access control
enforcement. If 'true', then enforcement
is enabled. If 'false', then enforcement
is disabled.";
}
leaf read-default {
type action-type;
default "permit";
description
"Controls whether read access is granted if
no appropriate rule is found for a
particular read request.";
}
leaf write-default {
type action-type;
default "deny";
description
"Controls whether create, update, or delete access
is granted if no appropriate rule is found for a
particular write request.";
}
leaf exec-default {
type action-type;
default "permit";
description
"Controls whether exec access is granted if no appropriate
rule is found for a particular protocol operation request.";
}
leaf enable-external-groups {
type boolean;
default true;
description
"Controls whether the server uses the groups reported by the
NETCONF transport layer when it assigns the user to a set of
NACM groups. If this leaf has the value 'false', any group
names reported by the transport layer are ignored by the
server.";
}
leaf denied-operations {
type yang:zero-based-counter32;
config false;
mandatory true;
description
"Number of times since the server last restarted that a
protocol operation request was denied.";
}
leaf denied-data-writes {
type yang:zero-based-counter32;
config false;
mandatory true;
description
"Number of times since the server last restarted that a
protocol operation request to alter
a configuration datastore was denied.";
}
leaf denied-notifications {
type yang:zero-based-counter32;
config false;
mandatory true;
description
"Number of times since the server last restarted that
a notification was dropped for a subscription because
access to the event type was denied.";
}
container groups {
description
"NETCONF Access Control Groups.";
list group {
key name;
description
"One NACM Group Entry. This list will only contain
configured entries, not any entries learned from
any transport protocols.";
leaf name {
type group-name-type;
description
"Group name associated with this entry.";
}
leaf-list user-name {
type user-name-type;
description
"Each entry identifies the username of
a member of the group associated with
this entry.";
}
}
}
list rule-list {
key "name";
ordered-by user;
description
"An ordered collection of access control rules.";
leaf name {
type string {
length "1..max";
}
description
"Arbitrary name assigned to the rule-list.";
}
leaf-list group {
type union {
type matchall-string-type;
type group-name-type;
}
description
"List of administrative groups that will be
assigned the associated access rights
defined by the 'rule' list.
The string '*' indicates that all groups apply to the
entry.";
}
list rule {
key "name";
ordered-by user;
description
"One access control rule.
Rules are processed in user-defined order until a match is
found. A rule matches if 'module-name', 'rule-type', and
'access-operations' match the request. If a rule
matches, the 'action' leaf determines if access is granted
or not.";
leaf name {
type string {
length "1..max";
}
description
"Arbitrary name assigned to the rule.";
}
leaf module-name {
type union {
type matchall-string-type;
type string;
}
default "*";
description
"Name of the module associated with this rule.
This leaf matches if it has the value '*' or if the
object being accessed is defined in the module with the
specified module name.";
}
choice rule-type {
description
"This choice matches if all leafs present in the rule
match the request. If no leafs are present, the
choice matches all requests.";
case protocol-operation {
leaf rpc-name {
type union {
type matchall-string-type;
type string;
}
description
"This leaf matches if it has the value '*' or if
its value equals the requested protocol operation
name.";
}
}
case notification {
leaf notification-name {
type union {
type matchall-string-type;
type string;
}
description
"This leaf matches if it has the value '*' or if its
value equals the requested notification name.";
}
}
case data-node {
leaf path {
type node-instance-identifier;
mandatory true;
description
"Data Node Instance Identifier associated with the
data node controlled by this rule.
Configuration data or state data instance
identifiers start with a top-level data node. A
complete instance identifier is required for this
type of path value.
The special value '/' refers to all possible
datastore contents.";
}
}
}
leaf access-operations {
type union {
type matchall-string-type;
type access-operations-type;
}
default "*";
description
"Access operations associated with this rule.
This leaf matches if it has the value '*' or if the
bit corresponding to the requested operation is set.";
}
leaf action {
type action-type;
mandatory true;
description
"The access control action associated with the
rule. If a rule is determined to match a
particular request, then this object is used
to determine whether to permit or deny the
request.";
}
leaf comment {
type string;
description
"A textual description of the access rule.";
}
}
}
}
}