* Bundle internal NETCONF on RESTCONF: A RESTCONF operation could produce several (up to four) internal NETCONF messages between RESTCONF server and backend. These have now been bundled into one.

* NACM recovery user session is now properly enforced. This means that if `CLICON_NACM_CREDENTIALS` is `except` (default), then a specific `CLICON_NACM_RECOVERY_USER` can make any edits and bypass NACM rules.
* If a default value is replaced by an actual value, RESTCONF return values have changed from `204 No Content` to `201 Created`
* clixon-config.yang: Removed default valude of CLICON_NACM_RECOVERY_USER
This commit is contained in:
Olof hagsand 2020-08-11 14:46:53 +02:00
parent de85f20415
commit dc1ad560f9
12 changed files with 217 additions and 261 deletions

View file

@ -576,6 +576,9 @@ from_client_edit_config(clicon_handle h,
int ret;
char *username;
cxobj *xret = NULL;
char *attr;
int autocommit = 0;
char *val = NULL;
username = clicon_username_get(h);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -605,7 +608,6 @@ from_client_edit_config(clicon_handle h,
goto done;
goto ok;
}
if ((x = xpath_first(xn, NULL, "default-operation")) != NULL){
if (xml_operation(xml_body(x), &operation) < 0){
if (netconf_invalid_value(cbret, "protocol", "Wrong operation")< 0)
@ -660,8 +662,41 @@ from_client_edit_config(clicon_handle h,
if (ret == 0)
goto ok;
xmldb_modified_set(h, target, 1); /* mark as dirty */
/* Clixon extension: autocommit */
if ((attr = xml_find_value(xn, "autocommit")) != NULL &&
strcmp(attr,"true")==0)
autocommit = 1;
/* If autocommit option is set or requested by client */
if (clicon_autocommit(h) || autocommit) {
if ((ret = candidate_commit(h, "candidate", cbret)) < 0){ /* Assume validation fail, nofatal */
if (ret < 0)
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
goto ok;
}
if (ret == 0){ /* discard */
if (xmldb_copy(h, "running", "candidate") < 0){
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
goto ok;
}
goto ok;
}
}
/* Clixon extension: copy */
if ((attr = xml_find_value(xn, "copystartup")) != NULL &&
strcmp(attr,"true") == 0){
if (xmldb_copy(h, "running", "startup") < 0){
if (netconf_operation_failed(cbret, "application", clicon_err_reason)< 0)
goto done;
goto ok;
}
}
assert(cbuf_len(cbret) == 0);
cprintf(cbret, "<rpc-reply><ok/></rpc-reply>");
cprintf(cbret, "<rpc-reply><ok");
if (clicon_data_get(h, "objectexisted", &val) == 0)
cprintf(cbret, " objectexisted=\"%s\"", val);
cprintf(cbret, "/></rpc-reply>");
ok:
retval = 0;
done:
@ -1026,7 +1061,7 @@ from_client_get(clicon_handle h,
content = netconf_content_str2int(attr);
/* Clixon extensions: depth */
if ((attr = xml_find_value(xe, "depth")) != NULL){
char *reason = NULL;
char *reason = NULL;
if ((ret = parse_int32(attr, &depth, &reason)) < 0){
clicon_err(OE_XML, errno, "parse_int32");
goto done;

View file

@ -312,10 +312,6 @@ cli_dbxml(clicon_handle h,
goto done;
if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
goto done;
if (clicon_autocommit(h)) {
if (clicon_rpc_commit(h) < 0)
goto done;
}
retval = 0;
done:
if (xerr)

View file

@ -258,7 +258,8 @@ restconf_terminate(clicon_handle h)
return 0;
}
/*! If restconf insert/point attributes are present, translate to netconf
/*! If RESTCONF insert/point attributes are present, translate to NETCONF
*
* @param[in] xdata URI->XML to translate
* @param[in] qvec Query parameters (eg where insert/point should be)
* @retval 0 OK

View file

@ -272,7 +272,8 @@ api_data_write(clicon_handle h,
cvec *nsc = NULL;
yang_bind yb;
char *xpath = NULL;
char *attr;
clicon_debug(1, "%s api_path:\"%s\"", __FUNCTION__, api_path0);
clicon_debug(1, "%s data:\"%s\"", __FUNCTION__, data);
if ((yspec = clicon_dbspec_yang(h)) == NULL){
@ -297,43 +298,10 @@ api_data_write(clicon_handle h,
goto ok;
}
}
xret = NULL;
if (clicon_rpc_get_config(h, clicon_nacm_recovery_user(h),
"candidate", xpath, nsc, &xret) < 0){
if (netconf_operation_failed_xml(&xerr, "protocol", clicon_err_reason) < 0)
goto done;
if ((xe = xpath_first(xerr, NULL, "rpc-error")) == NULL){
clicon_err(OE_XML, EINVAL, "rpc-error not found (internal error)");
goto done;
}
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
#if 0
if (clicon_debug_get())
clicon_log_xml(LOG_DEBUG, xret, "%s xret:", __FUNCTION__);
#endif
if (xml_child_nr(xret) == 0){ /* Object does not exist */
if (plain_patch){ /* If the target resource instance does not exist, the server MUST NOT create it. */
restconf_badrequest(h, req);
goto ok;
}
else
op = OP_CREATE;
}
else{
if (plain_patch)
op = OP_MERGE;
else
op = OP_REPLACE;
}
if (xret){
xml_free(xret);
xret = NULL;
}
if (plain_patch)
op = OP_MERGE; /* bad request if it does not exist */
else
op = OP_REPLACE; /* OP_CREATE if it does not exist */
/* Create config top-of-tree */
if ((xtop = xml_new("config", NULL, CX_ELMNT)) == NULL)
goto done;
@ -491,9 +459,20 @@ api_data_write(clicon_handle h,
goto done;
if (xml_prefix_set(xa, NETCONF_BASE_PREFIX) < 0)
goto done;
if (xml_value_set(xa, xml_operation2str(op)) < 0)
if (xml_value_set(xa, xml_operation2str(op)) < 0) /* XXX here is where op is used */
goto done;
if ((xa = xml_new("objectcreate", xdata, CX_ATTR)) == NULL)
goto done;
if (plain_patch){
/* RFC 8040 4.6. PATCH:
* If the target resource instance does not exist, the server MUST NOT create it.
*/
if (xml_value_set(xa, "false") < 0)
goto done;
}
else
if (xml_value_set(xa, "true") < 0)
goto done;
/* Top-of tree, no api-path
* Replace xparent with x, ie bottom of api-path with data
*/
@ -608,7 +587,17 @@ api_data_write(clicon_handle h,
username?username:"",
NETCONF_BASE_PREFIX,
NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */
cprintf(cbx, "<edit-config><target><candidate /></target>");
cprintf(cbx, "<edit-config");
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
*/
if (if_feature(yspec, "ietf-netconf", "startup"))
cprintf(cbx, " copystartup=\"true\"");
cprintf(cbx, " autocommit=\"true\"");
cprintf(cbx, "><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
if (clicon_xml2cbuf(cbx, xtop, 0, 0, -1) < 0)
goto done;
@ -621,58 +610,15 @@ api_data_write(clicon_handle h,
goto done;
goto ok;
}
cbuf_reset(cbx);
/* commit/discard should be done automaticaly by the system, therefore
* recovery user is used here (edit-config but not commit may be permitted
by NACM */
cprintf(cbx, "<rpc username=\"%s\">", clicon_nacm_recovery_user(h));
cprintf(cbx, "<commit/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<discard-changes/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0)
if ((xe = xpath_first(xret, NULL, "//ok")) != NULL &&
(attr = xml_find_value(xe, "objectexisted")) != NULL &&
strcmp(attr, "false")==0){
if (restconf_reply_send(req, 201, NULL) < 0) /* Created */
goto done;
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (xretcom){ /* Clear: can be reused again below */
xml_free(xretcom);
xretcom = NULL;
}
if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
*/
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", clicon_nacm_recovery_user(h));
cprintf(cbx, "<copy-config><source><running/></source><target><startup/></target></copy-config></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
/* If copy-config failed, log and ignore (already committed) */
if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
}
}
/* Check if it was created, or if we tried again and replaced it */
if (op == OP_CREATE){
if (restconf_reply_send(req, 201, NULL) < 0)
else
if (restconf_reply_send(req, 204, NULL) < 0) /* No content */
goto done;
}
else{
if (restconf_reply_send(req, 204, NULL) < 0)
goto done;
}
ok:
retval = 0;
done:
@ -877,7 +823,18 @@ api_data_delete(clicon_handle h,
username?username:"",
NETCONF_BASE_PREFIX,
NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */
cprintf(cbx, "<edit-config><target><candidate /></target>");
cprintf(cbx, "<edit-config");
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
*/
if (if_feature(yspec, "ietf-netconf", "startup"))
cprintf(cbx, " copystartup=\"true\"");
cprintf(cbx, " autocommit=\"true\"");
cprintf(cbx, "><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
if (clicon_xml2cbuf(cbx, xtop, 0, 0, -1) < 0)
goto done;
@ -889,50 +846,6 @@ api_data_delete(clicon_handle h,
goto done;
goto ok;
}
/* Assume this is validation failed since commit includes validate */
cbuf_reset(cbx);
/* commit/discard should be done automatically by the system, therefore
* recovery user is used here (edit-config but not commit may be permitted
by NACM */
cprintf(cbx, "<rpc username=\"%s\">", clicon_nacm_recovery_user(h));
cprintf(cbx, "<commit/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", clicon_nacm_recovery_user(h));
cprintf(cbx, "<discard-changes/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0)
goto done;
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0)
goto done;
goto ok;
}
if (xretcom){ /* Clear: can be reused again below */
xml_free(xretcom);
xretcom = NULL;
}
if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
*/
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", clicon_nacm_recovery_user(h));
cprintf(cbx, "<copy-config><source><running/></source><target><startup/></target></copy-config></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
/* If copy-config failed, log and ignore (already committed) */
if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
}
}
if (restconf_reply_send(req, 204, NULL) < 0)
goto done;
ok:

View file

@ -370,7 +370,18 @@ api_data_post(clicon_handle h,
username?username:"",
NETCONF_BASE_PREFIX,
NETCONF_BASE_NAMESPACE); /* bind nc to netconf namespace */
cprintf(cbx, "<edit-config><target><candidate /></target>");
cprintf(cbx, "<edit-config");
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
*/
if (if_feature(yspec, "ietf-netconf", "startup"))
cprintf(cbx, " copystartup=\"true\"");
cprintf(cbx, " autocommit=\"true\"");
cprintf(cbx, "><target><candidate /></target>");
cprintf(cbx, "<default-operation>none</default-operation>");
if (clicon_xml2cbuf(cbx, xtop, 0, 0, -1) < 0)
goto done;
@ -383,49 +394,6 @@ api_data_post(clicon_handle h,
goto done;
goto ok;
}
/* Assume this is validation failed since commit includes validate */
cbuf_reset(cbx);
/* commit/discard should be done automaticaly by the system, therefore
* recovery user is used here (edit-config but not commit may be permitted
by NACM */
cprintf(cbx, "<rpc username=\"%s\">", clicon_nacm_recovery_user(h));
cprintf(cbx, "<commit/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", username?username:"");
cprintf(cbx, "<discard-changes/></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretdis, NULL) < 0)
goto done;
/* log errors from discard, but ignore */
if ((xpath_first(xretdis, NULL, "//rpc-error")) != NULL)
clicon_log(LOG_WARNING, "%s: discard-changes failed which may lead candidate in an inconsistent state", __FUNCTION__);
if (api_return_err(h, req, xe, pretty, media_out, 0) < 0) /* Use original xe */
goto done;
goto ok;
}
if (xretcom){ /* Clear: can be reused again below */
xml_free(xretcom);
xretcom = NULL;
}
if (if_feature(yspec, "ietf-netconf", "startup")){
/* RFC8040 Sec 1.4:
* If the NETCONF server supports :startup, the RESTCONF server MUST
* automatically update the non-volatile startup configuration
* datastore, after the "running" datastore has been altered as a
* consequence of a RESTCONF edit operation.
*/
cbuf_reset(cbx);
cprintf(cbx, "<rpc username=\"%s\">", clicon_nacm_recovery_user(h));
cprintf(cbx, "<copy-config><source><running/></source><target><startup/></target></copy-config></rpc>");
if (clicon_rpc_netconf(h, cbuf_get(cbx), &xretcom, NULL) < 0)
goto done;
/* If copy-config failed, log and ignore (already committed) */
if ((xe = xpath_first(xretcom, NULL, "//rpc-error")) != NULL){
clicon_log(LOG_WARNING, "%s: copy-config running->startup failed", __FUNCTION__);
}
}
if (http_location_header(h, req, xdata) < 0)
goto done;
if (restconf_reply_send(req, 201, NULL) < 0)