* Added Clixon Restconf library

* Builds and installs a new restconf library: libclixon_restconf.so and clixon_restconf.h
  * The restconf library can be included by a restconf plugin.
  * Example code in example/Makefile.in and example/restconf_lib.c
* Authorization
  * Example extended with authorization
  * Test added with http basic authorization (test/test_auth.sh)
  * Documentation in FAQ.md
* README.md extended with new yang, netconf, restconf, datastore, and auth sections.
This commit is contained in:
Olof hagsand 2018-03-25 15:47:27 +02:00
parent cce76faa79
commit bfce20c760
17 changed files with 773 additions and 157 deletions

View file

@ -4,6 +4,14 @@
### Major changes:
* Added Clixon Restconf library
* Builds and installs a new restconf library: libclixon_restconf.so and clixon_restconf.h
* The restconf library can be included by a restconf plugin.
* Example code in example/Makefile.in and example/restconf_lib.c
* Authorization
* Example extended with authorization
* Test added with http basic authorization (test/test_auth.sh)
* Documentation in FAQ.md
* Restconf error handling for get, put and post. Several cornercases remain. Available both as xml and json (set accept header), pretty-printed and not (set clixon config option).
* Proper RFC 6241 Netconf error handling
* New functions added in clixon_netconf_lib.[ch]
@ -12,6 +20,7 @@
### Minor changes:
* README.md extended with new yang, netconf, restconf, datastore, and auth sections.
* The key-value datastore is no longer supported. Use the default text datastore.
* Add username to rpc calls to prepare for authorization for backend:
clicon_rpc_config_get(h, db, xpath, xt) --> clicon_rpc_config_get(h, db, xpath, username, xt)

184
README.md
View file

@ -4,26 +4,43 @@ Clixon is an automatic configuration manager where you generate
interactive CLI, NETCONF, RESTCONF and embedded databases with
transaction support from a YANG specification.
Table of contents
=================
* [Documentation](#documentation)
* [Installation](#installation)
* [Dependencies](#dependencies)
* [Licenses](#licenses)
Topics
======
* [Background](#background)
* [Frequently asked questions](doc/FAQ.md)
* [Installation](#installation)
* [Licenses](#licenses)
* [Support](#support)
* [Dependencies](#dependencies)
* [Extending](#extending)
* [Yang](#yang)
* [Netconf](#netconf)
* [Restconf](#restconf)
* [Datastore](datastore/README.md)
* [Authentication and Authorization](#auth)
* [Example](example/README.md)
* [Changelog](CHANGELOG.md) recent changes.
* [Clixon SDK](#SDK)
* [Clicon and Clixon project page](http://www.clicon.org)
* [Tests](test/README.md)
* [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`)
Documentation
=============
- [Frequently asked questions](doc/FAQ.md)
- [CHANGELOG](CHANGELOG.md) recent changes.
- [XML datastore](datastore/README.md)
- [Netconf support](apps/netconf/README.md)
- [Restconf support](apps/restconf/README.md)
- [Reference manual](http://www.clicon.org/doxygen/index.html) (Note: the link may not be up-to-date. It is better to build your own: `cd doc; make doc`)
- [Routing example](example/README.md)
- [Clicon and Clixon project page](http://www.clicon.org)
- [Tests](test/README.md)
Background
==========
Clixon was implemented to provide an open-source generic configuration
tool. The existing [CLIgen](http://www.cligen.se) tool was extended to
a framework. Most of the user projects are for embedded network and
measuring devices, but can be deployed for more general use.
Users of clixon currently include:
* [Netgate](https://www.netgate.com)
* [CloudMon360](http://cloudmon360.com)
* [Grideye](http://hagsand.se/grideye)
* [Netclean](https://www.netclean.com/solutions/whitebox) (only CLIgen)
* [Prosilient's PTAnalyzer]<a href="http://www.prosilient.com"> (only CLIgen)
See also [Clicon project page](http://clicon.org).
Installation
============
@ -38,6 +55,13 @@ A typical installation is as follows:
One [example application](example/README.md) is provided, a IETF IP YANG datamodel with
generated CLI and configuration interface.
Licenses
========
Clixon is open-source and dual licensed. Either Apache License, Version 2.0 or GNU
General Public License Version 2; you choose.
See [LICENSE.md](LICENSE.md) for the license.
Dependencies
============
Clixon depends on the following software packages, which need to exist on the target machine.
@ -50,53 +74,105 @@ to build and install CLIgen:
- Yacc/bison
- Lex/Flex
- Fcgi (if restconf is enabled)
- [Qdbm](http://fallabs.com/qdbm/) key-value store (if keyvalue datastore is enabled)
There is no yum/apt/ostree package for Clixon (please help?)
Licenses
Support
=======
Clixon interaction is best done posting issues, pull requests, or joining the [slack channel](https://join.slack.com/t/clixondev/shared_invite/enQtMzI3OTM4MzA3Nzk3LTA3NWM4OWYwYWMxZDhiYTNhNjRkNjQ1NWI1Zjk5M2JjMDk4MTUzMTljYTZiYmNhODkwMDI2ZTkyNWU3ZWMyN2U).
Extending
=========
Clixon provides a core system and can be used as-is using available
Yang specifications. However, an application very quickly needs to
specialize funxtions. Clixon is extended by (most commonly) writing
plugins for cli and backend. Extensions for netconf and restconf
are also available.
Plugins are written in C and easiest is to look at
[example](example/README.md) or consulting the [FAQ](doc/FAQ.md).
Yang
====
YANG and XML is at the heart of Clixon. Yang modules are used as a
specification for handling XML configuration data. The YANG spec is
used to generate an interactive CLI, netconf and restconf clients. It
also manages an XML datastore.
Clixon mainly follows [YANG 1.0 RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) with some exceptions:
- conformance: feature, if-feature, deviation
- identity, base, identityref
- list features: min/max-elements, unique
The aim is also to cover new featires in YANG 1.1 [YANG RFC 7950](https://www.rfc-editor.org/rfc/rfc7950.txt)
Clixon has its own XML library designed for performance.
Netconf
=======
Clixon implements the following NETCONF proposals or standards:
- [NETCONF Configuration Protocol](http://www.rfc-base.org/txt/rfc-4741.txt)
- [Using the NETCONF Configuration Protocol over Secure SHell (SSH)](http://www.rfc-base.org/txt/rfc-4742.txt)
- [NETCONF Event Notifications](http://www.rfc-base.org/txt/rfc-5277.txt)
Some updates are being made to RFC 6241 and RFC 6242.
Clixon does not support the following features:
- :url capability
- copy-config source config
- edit-config testopts
- edit-config erropts
- edit-config config-text
Restconf
========
Clixon is dual licensed. Either Apache License, Version 2.0 or GNU
General Public License Version 2; you choose.
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
run with NGINX.
The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
The following features are supported:
- OPTIONS, HEAD, GET, POST, PUT, DELETE
The following are not implemented
- PATCH
- query parameters (section 4.9)
- notifications (sec 6)
- schema resource
See [LICENSE.md](LICENSE.md) for the license.
See [more detailed restconf instructions](apps/restconf/README.md).
Background
==========
We implemented Clixon since we needed a generic configuration tool in
several projects, including
[KTH](http://www.csc.kth.se/~olofh/10G_OSR). Most of these projects
were for embedded network and measuring-probe devices. We started with
something called Clicon which was based on a key-value specification
and data-store. But as time passed new standards evolved and we
started adapting it to XML, Yang and netconf. Finally we made Clixon,
where the legacy key specification has been replaced completely by
YANG and using XML as configuration data. This means that legacy
Clicon applications do not run on Clixon.
Datastore
=========
The Clixon datastore is a stand-alone XML based datastore. The idea is
to be able to use different datastores backends with the same
API.
Update: There used to be a key-value plugin based on qdbm but isnow obsoleted. Only a text datastore is implemented.
The datastore is primarily designed to be used by Clixon but can be used
separately.
See [more detailed restconf instructions](datastore/README.md).
Auth
====
Authentication is not in-scope for Clixon, however, there is ongoing work
to implement [NACM](https://tools.ietf.org/html/rfc8341).
There are hooks (plugin callbacks) to identify which user is accessing a
client. That identity can then be used for authorization.
In short, authentication needs to be coupled to clixon clients:
* CLI - Login has already been made via SSH
* Netconf - SSH netconf subsystem
* Restconf needs credentials. See [FAQ](doc/FAQ.md#How-do-I-write-an-authentication-callback). The [Example](example/README.md) has an example how to do this with HTTP basic auth. It is possible for do this for more advanced mechanisms such as Oauth2 or [https://github.com/CESNET/Netopeer2/tree/master/server/configuration]
SDK
===
<img src="doc/clixon_example_sdk.png" alt="clixon sdk" style="width: 200px;"/>
<img src="doc/clixon_example_sdk.png" alt="clixon sdk" style="width: 180px;"/>
The figure shows the SDK runtime of Clixon.
YANG and XML is at the heart of Clixon. Yang modules are used as a
specification for handling XML configuration data. The spec is also
used to generate an interactive CLI client as well as provide
[Netconf](apps/netconf/README.md) and
[Restconf](apps/restconf/README.md) clients.
The [YANG RFC 6020](https://www.rfc-editor.org/rfc/rfc6020.txt) is implemented with the following exceptions:
- conformance: feature, if-feature, deviation
- identity, base, identityref
- list features: min/max-elements, unique, ordered-by
There are also new features in YANG 1.1 [YANG RFC
7950](https://www.rfc-editor.org/rfc/rfc7950.txt), most of which are
not implemented.

View file

@ -44,6 +44,7 @@ bindir = @bindir@
libdir = @libdir@
mandir = @mandir@
libexecdir = @libexecdir@
wwwdir = /www-data
localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
includedir = @includedir@

View file

@ -201,7 +201,6 @@ netconf_input_cb(int s,
retval = 0;
goto done;
}
for (i=0; i<len; i++){
if (buf[i] == 0)
continue; /* Skip NULL chars (eg from terminals) */

View file

@ -38,12 +38,17 @@ CFLAGS = @CFLAGS@
LDFLAGS = @LDFLAGS@
prefix = @prefix@
datarootdir = @datarootdir@
exec_prefix = @exec_prefix@
bindir = @bindir@
wwwdir = /www-data
libdir = @libdir@
mandir = @mandir@
libexecdir = @libexecdir@
wwwdir = /www-data
localstatedir = @localstatedir@
sysconfdir = @sysconfdir@
includedir = @includedir@
HOST_VENDOR = @host_vendor@
SH_SUFFIX = @SH_SUFFIX@
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
@ -61,8 +66,7 @@ CPPFLAGS = @CPPFLAGS@
INCLUDES = -I. -I$(top_srcdir)/lib/src -I$(top_srcdir)/lib -I$(top_srcdir)/include -I$(top_srcdir) @INCLUDES@
SRC = restconf_lib.c
SRC += restconf_methods.c
SRC = restconf_methods.c
OBJS = $(SRC:.c=.o)
@ -70,10 +74,18 @@ APPSRC = restconf_main.c
APPOBJ = $(APPSRC:.c=.o)
APPL = clixon_restconf
all: $(APPL)
MYNAME = clixon_restconf
MYLIBLINK = lib$(MYNAME)$(SH_SUFFIX)
MYLIB = $(MYLIBLINK).$(CLIXON_MAJOR).$(CLIXON_MINOR)
MYLIBSO = $(MYLIBLINK).$(CLIXON_MAJOR)
LIBSRC = restconf_lib.c
LIBOBJS = $(LIBSRC:.c=.o)
all: $(MYLIB) $(APPL)
clean:
rm -f $(OBJS) *.core $(APPL) $(APPOBJ)
rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(APPOBJ) $(MYLIB) $(MYLIBSO) $(MYLIBLINK)
distclean: clean
rm -f Makefile *~ .depend
@ -82,14 +94,23 @@ distclean: clean
# Put other executables in libexec/
# Also create a libexec/ directory for writeable/temporary files.
# Put config file in etc/
install: $(APPL)
install: install-lib $(APPL)
install -d $(DESTDIR)$(wwwdir)
install $(APPL) $(DESTDIR)$(wwwdir)
install-include:
install-lib: $(MYLIB)
install -d $(DESTDIR)$(libdir)
install $(MYLIB) $(DESTDIR)$(libdir)
ln -sf $(MYLIB) $(DESTDIR)$(libdir)/$(MYLIBSO) # -l:libclixon_restconf.so.2
ln -sf $(MYLIBSO) $(DESTDIR)$(libdir)/$(MYLIBLINK) # -l:libclixon_restconf.so
install-include: clixon_restconf.h
install -d $(DESTDIR)$(includedir)/clixon
install -m 644 $^ $(DESTDIR)$(includedir)/clixon
uninstall:
rm -f $(DESTDIR)$(wwwdir)/$(APPL)
rm -f $(DESTDIR)$(libdir)/$(MYLIBLINK)*
.SUFFIXES:
.SUFFIXES: .c .o
@ -97,8 +118,18 @@ uninstall:
.c.o:
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $<
$(APPL) : $(APPOBJ) $(OBJS) $(LIBDEPS)
$(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) $(LIBS) -o $@
$(APPL) : $(APPOBJ) $(MYLIBLINK) $(OBJS) $(LIBDEPS)
$(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) -L. $(MYLIB) $(LIBS) -o $@
$(MYLIB) : $(LIBOBJS)
ifeq ($(HOST_VENDOR),apple)
$(CC) $(LDFLAGS) -shared -undefined dynamic_lookup -o $@ $(LIBOBJS) $(LIBS)
else
$(CC) $(LDFLAGS) -shared -Wl,-soname,$(MYLIBSO) -o $@ $(LIBOBJS) $(LIBS) -Wl,-soname=$(MYLIBSO)
endif
# link-name is needed for application linking, eg for clixon_cli and clixon_config
$(MYLIBLINK) : $(MYLIB)
TAGS:
find . -name '*.[chyl]' -print | etags -

View file

@ -1,16 +1,4 @@
# Clixon Restconf
### Features
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
run with NGINX.
The implementatation is based on [RFC 8040: RESTCONF Protocol](https://tools.ietf.org/html/rfc8040).
The following featires are supported:
- OPTIONS, HEAD, GET, POST, PUT, DELETE
The following are not implemented
- PATCH
- query parameters (section 4.9)
- notifications (sec 6)
- schema resource
### Installation using Nginx

View file

@ -0,0 +1,71 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
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 *****
* The exported interface to plugins. External apps (eg frontend restconf plugins)
* should only include this file (not the restconf_*.h)
*/
#ifndef _CLIXON_RESTCONF_H_
#define _CLIXON_RESTCONF_H_
/*
* Constants
*/
/*
* Prototypes
* (Duplicated. Also in restconf_*.h)
*/
int restconf_err2code(char *tag);
const char *restconf_code2reason(int code);
int badrequest(FCGX_Request *r);
int unauthorized(FCGX_Request *r);
int forbidden(FCGX_Request *r);
int notfound(FCGX_Request *r);
int conflict(FCGX_Request *r);
int internal_server_error(FCGX_Request *r);
int notimplemented(FCGX_Request *r);
int clicon_debug_xml(int dbglevel, char *str, cxobj *cx);
int test(FCGX_Request *r, int dbg);
cbuf *readdata(FCGX_Request *r);
int restconf_plugin_load(clicon_handle h);
int restconf_plugin_start(clicon_handle h, int argc, char **argv);
int restconf_plugin_unload(clicon_handle h);
int restconf_credentials(clicon_handle h, FCGX_Request *r, char **user);
int get_user_cookie(char *cookiestr, char *attribute, char **val);
#endif /* _CLIXON_RESTCONF_H_ */

View file

@ -327,6 +327,7 @@ test(FCGX_Request *r,
printparam(r, "HTTPS", dbg);
printparam(r, "HTTP_ACCEPT", dbg);
printparam(r, "HTTP_CONTENT_TYPE", dbg);
printparam(r, "HTTP_AUTHORIZATION", dbg);
#if 0 /* For debug */
clicon_debug(1, "All environment vars:");
{
@ -375,6 +376,7 @@ restconf_plugin_load(clicon_handle h)
plghndl_t *handle;
char filename[MAXPATHLEN];
clicon_debug(1, "%s", __FUNCTION__);
if ((dir = clicon_restconf_dir(h)) == NULL){
retval = 0;
goto quit;
@ -382,7 +384,6 @@ restconf_plugin_load(clicon_handle h)
/* Get plugin objects names from plugin directory */
if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0)
goto quit;
/* Load all plugins */
for (i = 0; i < ndp; i++) {
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);

View file

@ -304,3 +304,21 @@ fib_route(clicon_handle h, /* Clicon handle */
}
```
Here, the callback is over-simplified.
## How do I write an authentication callback?
A restconf call may need to be authenticated.
You can specify an authentication callback for restconf as follows:
```
int
plugin_credentials(clicon_handle h,
FCGX_Request *r,
char **username)
```
If a plugin is provided, it needs to supply a username. If not, the
request is unauthorized. the function mallocs a username and returns
it.
See [../apps/example/routing_restconf.c] plugin_credentials() for
an example of HTTP basic auth.

View file

@ -47,8 +47,9 @@ INCLUDES = -I$(includedir) @INCLUDES@
BE_PLUGIN = $(APPNAME)_backend.so
CLI_PLUGIN = $(APPNAME)_cli.so
NETCONF_PLUGIN = $(APPNAME)_netconf.so
RESTCONF_PLUGIN = $(APPNAME)_restconf.so
PLUGINS = $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN)
PLUGINS = $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN)
all: $(PLUGINS)
@ -73,22 +74,28 @@ YANGSPECS += example.yang
BE_SRC = routing_backend.c
BE_OBJ = $(BE_SRC:%.c=%.o)
$(BE_PLUGIN): $(BE_OBJ)
$(CC) -shared -o $@ -lc $<
$(CC) -Wall -shared -o $@ -lc $<
# CLI frontend plugin
CLI_SRC = routing_cli.c
CLI_OBJ = $(CLI_SRC:%.c=%.o)
$(CLI_PLUGIN): $(CLI_OBJ)
$(CC) -shared -o $@ -lc $^
$(CC) -Wall -shared -o $@ -lc $^
# NETCONF frontend plugin
NETCONF_SRC = routing_netconf.c
NETCONF_OBJ = $(NETCONF_SRC:%.c=%.o)
$(NETCONF_PLUGIN): $(NETCONF_OBJ)
$(CC) -shared -o $@ -lc $^
$(CC) -Wall -shared -o $@ -lc $^
SRC = $(BE_SRC) $(CLI_SRC) $(NETCONF_SRC)
OBJS = $(BE_OBJ) $(CLI_OBJ) $(NETCONF_OBJ)
# RESTCONF frontend plugin
RESTCONF_SRC = routing_restconf.c
RESTCONF_OBJ = $(RESTCONF_SRC:%.c=%.o)
$(RESTCONF_PLUGIN): $(RESTCONF_OBJ)
$(CC) -Wall -shared -o $@ -lc $^
SRC = $(BE_SRC) $(CLI_SRC) $(NETCONF_SRC) $(RESTCONF_SRC)
OBJS = $(BE_OBJ) $(CLI_OBJ) $(NETCONF_OBJ) $(RESTCONF_OBJ)
clean:
rm -f $(PLUGINS) $(OBJS)
@ -98,7 +105,7 @@ distclean: clean
rm -f Makefile *~ .depend
(cd docker && $(MAKE) $(MFLAGS) $@)
install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(APPNAME).xml
install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $(RESTCONF_PLUGIN) $(APPNAME).xml
install -d $(DESTDIR)$(clixon_SYSCONFDIR)
install $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR)
install -d $(DESTDIR)$(clixon_DBSPECDIR)/yang
@ -109,6 +116,8 @@ install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $
install $(BE_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend;
install -d $(DESTDIR)$(clixon_LIBDIR)/netconf
install $(NETCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/netconf;
install -d $(DESTDIR)$(clixon_LIBDIR)/restconf
install $(RESTCONF_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/restconf;
install -d $(DESTDIR)$(clixon_LIBDIR)/clispec
install $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec;
install -d $(DESTDIR)$(clixon_LOCALSTATEDIR)

View file

@ -11,4 +11,21 @@ module example {
}
description
"Example code that includes ietf-ip and ietf-routing";
leaf basic_auth{
description "Basic user / password authentication as in HTTP basic auth";
type boolean;
default false;
}
list auth {
description "user / password entries. Valid if basic_auth=true";
key user;
leaf user{
description "User name";
type string;
}
leaf password{
description "Password";
type string;
}
}
}

287
example/routing_restconf.c Normal file
View file

@ -0,0 +1,287 @@
/*
*
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-2018 Olof Hagsand and Benny Holmgren
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 *****
*
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <signal.h>
#include <fcgi_stdio.h>
#include <curl/curl.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#include <clixon/clixon_restconf.h>
static const char Base64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char Pad64 = '=';
/* skips all whitespace anywhere.
converts characters, four at a time, starting at (or after)
src from base - 64 numbers into three 8 bit bytes in the target area.
it returns the number of data bytes stored at the target, or -1 on error.
@note what is copyright of this?
*/
int
b64_decode(const char *src,
char *target,
size_t targsize)
{
int tarindex, state, ch;
char *pos;
state = 0;
tarindex = 0;
while ((ch = *src++) != '\0') {
if (isspace(ch)) /* Skip whitespace anywhere. */
continue;
if (ch == Pad64)
break;
pos = strchr(Base64, ch);
if (pos == 0) /* A non-base64 character. */
return (-1);
switch (state) {
case 0:
if (target) {
if ((size_t)tarindex >= targsize)
return (-1);
target[tarindex] = (pos - Base64) << 2;
}
state = 1;
break;
case 1:
if (target) {
if ((size_t)tarindex + 1 >= targsize)
return (-1);
target[tarindex] |= (pos - Base64) >> 4;
target[tarindex+1] = ((pos - Base64) & 0x0f)
<< 4 ;
}
tarindex++;
state = 2;
break;
case 2:
if (target) {
if ((size_t)tarindex + 1 >= targsize)
return (-1);
target[tarindex] |= (pos - Base64) >> 2;
target[tarindex+1] = ((pos - Base64) & 0x03)
<< 6;
}
tarindex++;
state = 3;
break;
case 3:
if (target) {
if ((size_t)tarindex >= targsize)
return (-1);
target[tarindex] |= (pos - Base64);
}
tarindex++;
state = 0;
break;
default:
return -1;
}
}
/*
* We are done decoding Base-64 chars. Let's see if we ended
* on a byte boundary, and/or with erroneous trailing characters.
*/
if (ch == Pad64) { /* We got a pad char. */
ch = *src++; /* Skip it, get next. */
switch (state) {
case 0: /* Invalid = in first position */
case 1: /* Invalid = in second position */
return (-1);
case 2: /* Valid, means one byte of info */
/* Skip any number of spaces. */
for ((void)NULL; ch != '\0'; ch = *src++)
if (!isspace(ch))
break;
/* Make sure there is another trailing = sign. */
if (ch != Pad64)
return (-1);
ch = *src++; /* Skip the = */
/* Fall through to "single trailing =" case. */
/* FALLTHROUGH */
case 3: /* Valid, means two bytes of info */
/*
* We know this char is an =. Is there anything but
* whitespace after it?
*/
for ((void)NULL; ch != '\0'; ch = *src++)
if (!isspace(ch))
return (-1);
/*
* Now make sure for cases 2 and 3 that the "extra"
* bits that slopped past the last full byte were
* zeros. If we don't check them, they become a
* subliminal channel.
*/
if (target && target[tarindex] != 0)
return (-1);
}
} else {
/*
* We ended by seeing the end of the string. Make sure we
* have no partial bytes lying around.
*/
if (state != 0)
return (-1);
}
return (tarindex);
}
/*! Process a rest request that requires (cookie) "authentication"
* Note, this is loaded as dlsym fixed symbol in plugin
* @param[in] h Clixon handle
* @param[in] r Fastcgi request handle
* @param[out] username Malloced username, or NULL.
* @retval -1 Fatal error
* @retval 0 OK
* For grideye, return "u" entry name if it has a valid "user" entry.
*/
int
plugin_credentials(clicon_handle h,
FCGX_Request *r,
char **username)
{
int retval = -1;
cxobj *xt = NULL;
cxobj *x;
char *xbody;
char *auth;
char *user = NULL;
char *passwd;
char *passwd2;
size_t authlen;
cbuf *cb = NULL;
int ret;
clicon_debug(1, "%s", __FUNCTION__);
*username = NULL; /* unauthorized */
/* Check if basic_auth set, if not return OK */
if (clicon_rpc_get_config(h, "running", "/", NULL, &xt) < 0)
goto done;
if ((x = xpath_first(xt, "basic_auth")) == NULL)
goto none;
if ((xbody = xml_body(x)) == NULL)
goto none;
if (strcmp(xbody, "true"))
goto none;
/* At this point in the code we must use HTTP basic authentication */
if ((auth = FCGX_GetParam("HTTP_AUTHORIZATION", r->envp)) == NULL)
goto done;
if (strlen(auth) < strlen("Basic "))
goto fail;
if (strncmp("Basic ", auth, strlen("Basic ")))
goto fail;
auth += strlen("Basic ");
authlen = strlen(auth)*2;
if ((user = malloc(authlen)) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
goto done;
}
memset(user, 0, authlen);
if ((ret = b64_decode(auth, user, authlen)) < 0)
goto done;
/* auth string is on the format user:passwd */
if ((passwd = index(user,':')) == NULL)
goto fail;
*passwd = '\0';
passwd++;
clicon_debug(1, "%s user:%s passwd:%s", __FUNCTION__, user, passwd);
if ((cb = cbuf_new()) == NULL)
goto done;
cprintf(cb, "auth[user=%s]", user);
if ((x = xpath_first(xt, cbuf_get(cb))) == NULL)
goto fail;
passwd2 = xml_find_body(x, "password");
if (strcmp(passwd, passwd2))
goto fail;
if ((*username = strdup(user)) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
fail:
retval = 0;
done:
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
if (user)
free(user);
if (cb)
cbuf_free(cb);
if (xt)
xml_free(xt);
return retval;
none: /* basic_auth is not enabled, harcode authenticated user "none" */
if ((*username = strdup("none")) == NULL){
clicon_err(OE_XML, errno, "strdup");
goto done;
}
goto fail;
}
/*! Restconf plugin initialization
*/
int
plugin_init(clicon_handle h)
{
int retval = -1;
clicon_debug(1, "%s restconf", __FUNCTION__);
retval = 0;
// done:
return retval;
}

View file

@ -47,6 +47,10 @@ typedef void *plghndl_t;
/* Find plugin by name callback. XXX Should be clicon internal */
typedef void *(find_plugin_t)(clicon_handle, char *);
/*
* Prototypes
*/
@ -60,7 +64,7 @@ typedef void *(find_plugin_t)(clicon_handle, char *);
* @see plginit_t
*/
#define PLUGIN_INIT "plugin_init"
typedef int (plginit_t)(clicon_handle); /* Plugin Init */
typedef void * (plginit_t)(clicon_handle); /* Clixon plugin Init */
/* Called when backend started with cmd-line arguments from daemon call.
* @see plgstart_t
@ -86,6 +90,18 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
*/
typedef int (plgcredentials_t)(clicon_handle, void *, char **username);
/* grideye agent plugin init struct for the api
* Note: Implicit init function, see PLUGIN_INIT_FN_V2
*/
struct clixon_plugin_api{
plgcredentials_t *cp_auth;
};
/*
* Prototypes
*/
/* Find a function in global namespace or a plugin. XXX clicon internal */
void *clicon_find_func(clicon_handle h, char *plugin, char *func);

View file

@ -28,13 +28,17 @@ rm -rf $dir/*
# error and exit, arg is optional extra errmsg
err(){
echo "Error in Test$testnr [$testname]:"
echo -e "\e[31m\nError in Test$testnr [$testname]:"
if [ $# -gt 0 ]; then
echo "Expected: $1"
fi
if [ $# -gt 1 ]; then
echo "Received: $2"
fi
echo -e "\e[0m:"
echo "$ret"| od -t c > $dir/clixon-ret
echo "$expect"| od -t c > $dir/clixon-expect
diff $dir/clixon-ret $dir/clixon-expect
exit $testnr
}
@ -43,7 +47,11 @@ new(){
testnr=`expr $testnr + 1`
testname=$1
>&2 echo "Test$testnr [$1]"
# sleep 1
}
new2(){
testnr=`expr $testnr + 1`
testname=$1
>&2 echo -n "Test$testnr [$1]"
}
# clixon tester. First arg is command and second is expected outcome
@ -82,34 +90,15 @@ expectfn(){
fi
}
# Similar to expectfn, but checks for equality and not only match
expecteq2(){
expecteq(){
ret=$1
expect=$2
# Match if both are empty string
if [ -z "$ret" -a -z "$expect" ]; then
return
fi
if [ "$ret" != "$expect" ]; then
err "$expect" "$ret"
fi
}
# Similar to expectfn, but checks for equality and not only match
expecteq(){
cmd=$1
expect=$2
ret=$($cmd)
if [ $? -ne 0 ]; then
err "wrong args"
fi
# Match if both are empty string
if [ -z "$ret" -a -z "$expect" ]; then
return
fi
if [ "$ret" != "$expect" ]; then
if [[ "$ret" = "$expect" ]]; then
echo
else
err "$expect" "$ret"
fi
}

104
test/test_auth.sh Executable file
View file

@ -0,0 +1,104 @@
#!/bin/bash
# Authentication and authorization
# include err() and new() functions and creates $dir
. ./lib.sh
cfg=$dir/conf_yang.xml
fyang=$dir/test.yang
fyangerr=$dir/err.yang
cat <<EOF > $cfg
<config>
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/routing/yang</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>example</CLICON_YANG_MODULE_MAIN>
<CLICON_CLISPEC_DIR>/usr/local/lib/routing/clispec</CLICON_CLISPEC_DIR>
<CLICON_RESTCONF_DIR>/usr/local/lib/routing/restconf</CLICON_RESTCONF_DIR>
<CLICON_CLI_DIR>/usr/local/lib/routing/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>routing</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/routing/routing.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/routing/routing.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_CLI_GENMODEL_COMPLETION>1</CLICON_CLI_GENMODEL_COMPLETION>
<CLICON_XMLDB_DIR>/usr/local/var/routing</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PLUGIN>/usr/local/lib/xmldb/text.so</CLICON_XMLDB_PLUGIN>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
</config>
EOF
cat <<EOF > $fyang
module example{
prefix ex;
leaf basic_auth{
description "Basic user / password authentication as in HTTP basic auth";
type boolean;
default false;
}
list auth {
description "user / password entries. Valid if basic_auth=true";
key user;
leaf user{
description "User name";
type string;
}
leaf password{
description "Password";
type string;
}
}
}
EOF
# kill old backend (if any)
new "kill old backend"
sudo clixon_backend -zf $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "start backend -s init -f $cfg -y $fyang"
# start new backend
sudo clixon_backend -s init -f $cfg -y $fyang
if [ $? -ne 0 ]; then
err
fi
new "kill old restconf daemon"
sudo pkill -u www-data clixon_restconf
new "start restconf daemon"
sudo start-stop-daemon -S -q -o -b -x /www-data/clixon_restconf -d /www-data -c www-data -- -f $cfg # -D
sleep 1
new2 "auth get"
expecteq "$(curl -sS -X GET http://localhost/restconf/data)" '{"data": null}
'
new "auth set authentication config"
expecteof "$clixon_netconf -qf $cfg -y $fyang" "<rpc><edit-config><target><candidate/></target><config><basic_auth>true</basic_auth><auth><user>foo</user><password>bar</password></auth></config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
expecteof "$clixon_netconf -qf $cfg -y $fyang" "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new2 "auth get (access denied)"
expecteq "$(curl -sS -X GET http://localhost/restconf/data)" "<error-tag>access-denied</error-tag>
The requested URL /restconf/data was unauthorized."
new2 "auth get (access)"
expecteq "$(curl -u foo:bar -sS -X GET http://localhost/restconf/data)" '{"data": {"basic_auth": true,"auth": [{"user": "foo","password": "bar"}]}}
'
new "Kill restconf daemon"
sudo pkill -u www-data clixon_restconf
pid=`pgrep clixon_backend`
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err "kill backend"
fi
rm -rf $dir

View file

@ -78,22 +78,22 @@ sleep 1
new "restconf tests"
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
expecteq2 "$(curl -s -X GET http://localhost/.well-known/host-meta)" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
new2 "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
expecteq "$(curl -s -X GET http://localhost/.well-known/host-meta)" "<XRD xmlns='http://docs.oasis-open.org/ns/xri/xrd-1.0'>
<Link rel='restconf' href='/restconf'/>
</XRD> "
new "restconf get restconf resource. RFC 8040 3.3 (json)"
expecteq2 "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}}
new2 "restconf get restconf resource. RFC 8040 3.3 (json)"
expecteq "$(curl -sG http://localhost/restconf)" '{"restconf": {"data": null,"operations": null,"yang-library-version": "2016-06-21"}}
'
new "restconf get restconf resource. RFC 8040 3.3 (xml)"
new2 "restconf get restconf resource. RFC 8040 3.3 (xml)"
# Get XML instead of JSON?
expecteq2 "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '<restconf><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>
expecteq "$(curl -s -H 'Accept: application/yang-data+xml' -G http://localhost/restconf)" '<restconf><data/><operations/><yang-library-version>2016-06-21</yang-library-version></restconf>
'
new "restconf get restconf/operations. RFC8040 3.3.2 (json)"
expecteq2 "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}
new2 "restconf get restconf/operations. RFC8040 3.3.2 (json)"
expecteq "$(curl -sG http://localhost/restconf/operations)" '{"operations": {"ex:empty": null,"ex:input": null,"ex:output": null,"rt:fib-route": null,"rt:route-count": null}}
'
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
@ -104,8 +104,8 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
expecteq2 "$(curl -sG http://localhost/restconf/yang-library-version)" '{"yang-library-version": "2016-06-21"}'
new2 "restconf get restconf/yang-library-version. RFC8040 3.3.3"
expecteq "$(curl -sG http://localhost/restconf/yang-library-version)" '{"yang-library-version": "2016-06-21"}'
new "restconf get restconf/yang-library-version. RFC8040 3.3.3 (xml)"
ret=$(curl -s -H "Accept: application/yang-data+xml" -G http://localhost/restconf/yang-library-version)
@ -122,12 +122,12 @@ new "restconf head. RFC 8040 4.2"
expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
#Content-Type: application/yang-data+json"
new "restconf empty rpc"
expecteq2 "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null}
new2 "restconf empty rpc"
expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null}
'
new "restconf get empty config + state json"
expecteq2 "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
new2 "restconf get empty config + state json"
expecteq "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
'
new "restconf get empty config + state xml"
@ -138,8 +138,8 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf get data/interfaces-state/interface=eth0 json"
expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]}
new2 "restconf get data/interfaces-state/interface=eth0 json"
expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0)" '{"interface": [{"name": "eth0","type": "eth","if-index": 42}]}
'
new "restconf get state operation eth0 xml"
@ -151,8 +151,8 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf get state operation eth0 type json"
expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"}
new2 "restconf get state operation eth0 type json"
expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"}
'
new "restconf get state operation eth0 type xml"
@ -164,8 +164,8 @@ if [ -z "$match" ]; then
err "$expect" "$ret"
fi
new "restconf GET datastore"
expecteq2 "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
new2 "restconf GET datastore"
expecteq "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
'
# Exact match
@ -176,14 +176,14 @@ new "restconf Re-add subtree which should give error"
expectfn 'curl -s -X POST -d {"interfaces":{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
# XXX Cant get this to work
#expecteq2 "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
#expecteq "$(curl -s -X POST -d {\"interfaces\":{\"interface\":{\"name\":\"eth/0/0\",\"type\":\"eth\",\"enabled\":true}}} http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}}'
new "restconf Check interfaces eth/0/0 added"
expectfn "curl -s -G http://localhost/restconf/data" '{"interfaces": {"interface": \[{"name": "eth/0/0","type": "eth","enabled": true}\]},"interfaces-state": {"interface": \[{"name": "eth0","type": "eth","if-index": 42}\]}}
'
new "restconf delete interfaces"
expecteq2 $(curl -s -X DELETE http://localhost/restconf/data/interfaces) ""
new2 "restconf delete interfaces"
expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) ""
new "restconf Check empty config"
expectfn "curl -sG http://localhost/restconf/data" "$state"
@ -191,43 +191,43 @@ expectfn "curl -sG http://localhost/restconf/data" "$state"
new "restconf Add interfaces subtree eth/0/0 using POST"
expectfn 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' ""
# XXX cant get this to work
#expecteq2 "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" ""
#expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type\":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" ""
new "restconf Check eth/0/0 added"
expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
new2 "restconf Check eth/0/0 added"
expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
'
new "restconf Re-post eth/0/0 which should generate error"
expecteq 'curl -s -X POST -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} '
new2 "restconf Re-post eth/0/0 which should generate error"
expecteq "$(curl -s -X POST -d '{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}' http://localhost/restconf/data/interfaces)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} '
new "Add leaf description using POST"
expecteq 'curl -s -X POST -d {"description":"The-first-interface"} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
new2 "Add leaf description using POST"
expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" ""
new "Add nothing using POST"
expectfn 'curl -s -X POST http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' "data is in some way badly formed"
new "restconf Check description added"
expecteq "curl -s -G http://localhost/restconf/data" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
new2 "restconf Check description added"
expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","description": "The-first-interface","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
'
new "restconf delete eth/0/0"
expecteq 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" ""
new "Check deleted eth/0/0"
expectfn 'curl -s -G http://localhost/restconf/data' $state
new "restconf Re-Delete eth/0/0 using none should generate error"
expecteq 'curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} '
new2 "restconf Re-Delete eth/0/0 using none should generate error"
expecteq "$(curl -s -X DELETE http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-missing","error-type": "application","error-severity": "error","error-message": "Data does not exist; cannot delete resource"}}} '
new "restconf Add subtree eth/0/0 using PUT"
expecteq 'curl -s -X PUT -d {"interface":{"name":"eth/0/0","type":"eth","enabled":true}} http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0' ""
new2 "restconf Add subtree eth/0/0 using PUT"
expecteq "$(curl -s -X PUT -d '{"interface":{"name":"eth/0/0","type":"eth","enabled":true}}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" ""
new "restconf get subtree"
expecteq 'curl -s -G http://localhost/restconf/data' '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
new2 "restconf get subtree"
expecteq "$(curl -s -G http://localhost/restconf/data)" '{"data": {"interfaces": {"interface": [{"name": "eth/0/0","type": "eth","enabled": true}]},"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
'
new "restconf rpc using POST json"
expecteq 'curl -s -X POST -d {"input":{"routing-instance-name":"ipv4"}} http://localhost/restconf/operations/rt:fib-route' '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}}
new2 "restconf rpc using POST json"
expecteq "$(curl -s -X POST -d '{"input":{"routing-instance-name":"ipv4"}}' http://localhost/restconf/operations/rt:fib-route)" '{"output": {"route": {"address-family": "ipv4","next-hop": {"next-hop-list": "2.3.4.5"}}}}
'
new "restconf rpc using POST xml"

View file

@ -83,11 +83,11 @@ expectfn 'curl -s -X POST -d {"interface":{"name":"TEST"}} http://localhost/rest
new "restconf POST interface"
expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' ""
new "restconf POST again"
expecteq 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} '
new2 "restconf POST again"
expecteq "$(curl -s -X POST -d '{"interface":{"name":"TEST","type":"eth0"}}' http://localhost/restconf/data/cont1)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} '
new "restconf POST from top"
expecteq 'curl -s -X POST -d {"cont1":{"interface":{"name":"TEST","type":"eth0"}}} http://localhost/restconf/data' '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} '
new2 "restconf POST from top"
expecteq "$(curl -s -X POST -d '{"cont1":{"interface":{"name":"TEST","type":"eth0"}}}' http://localhost/restconf/data)" '{"ietf-restconf:errors" : {"error": {"error-tag": "data-exists","error-type": "application","error-severity": "error","error-message": "Data already exists; cannot create new resource"}}} '
new "restconf DELETE"
expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' ""