* 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:
parent
de85f20415
commit
dc1ad560f9
12 changed files with 217 additions and 261 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue