* 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:
parent
cce76faa79
commit
bfce20c760
17 changed files with 773 additions and 157 deletions
|
|
@ -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
184
README.md
|
|
@ -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.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ bindir = @bindir@
|
|||
libdir = @libdir@
|
||||
mandir = @mandir@
|
||||
libexecdir = @libexecdir@
|
||||
wwwdir = /www-data
|
||||
localstatedir = @localstatedir@
|
||||
sysconfdir = @sysconfdir@
|
||||
includedir = @includedir@
|
||||
|
|
|
|||
|
|
@ -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) */
|
||||
|
|
|
|||
|
|
@ -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 -
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
71
apps/restconf/clixon_restconf.h
Normal file
71
apps/restconf/clixon_restconf.h
Normal 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_ */
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
18
doc/FAQ.md
18
doc/FAQ.md
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
287
example/routing_restconf.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
39
test/lib.sh
39
test/lib.sh
|
|
@ -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
104
test/test_auth.sh
Executable 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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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' ""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue