* 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:
|
### 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).
|
* 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
|
* Proper RFC 6241 Netconf error handling
|
||||||
* New functions added in clixon_netconf_lib.[ch]
|
* New functions added in clixon_netconf_lib.[ch]
|
||||||
|
|
@ -12,6 +20,7 @@
|
||||||
|
|
||||||
### Minor changes:
|
### 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.
|
* The key-value datastore is no longer supported. Use the default text datastore.
|
||||||
* Add username to rpc calls to prepare for authorization for backend:
|
* 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)
|
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
|
interactive CLI, NETCONF, RESTCONF and embedded databases with
|
||||||
transaction support from a YANG specification.
|
transaction support from a YANG specification.
|
||||||
|
|
||||||
Table of contents
|
Topics
|
||||||
=================
|
======
|
||||||
* [Documentation](#documentation)
|
|
||||||
* [Installation](#installation)
|
|
||||||
* [Dependencies](#dependencies)
|
|
||||||
* [Licenses](#licenses)
|
|
||||||
* [Background](#background)
|
* [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)
|
* [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`)
|
||||||
|
|
||||||
|
Background
|
||||||
|
==========
|
||||||
|
|
||||||
Documentation
|
Clixon was implemented to provide an open-source generic configuration
|
||||||
=============
|
tool. The existing [CLIgen](http://www.cligen.se) tool was extended to
|
||||||
- [Frequently asked questions](doc/FAQ.md)
|
a framework. Most of the user projects are for embedded network and
|
||||||
- [CHANGELOG](CHANGELOG.md) recent changes.
|
measuring devices, but can be deployed for more general use.
|
||||||
- [XML datastore](datastore/README.md)
|
|
||||||
- [Netconf support](apps/netconf/README.md)
|
Users of clixon currently include:
|
||||||
- [Restconf support](apps/restconf/README.md)
|
* [Netgate](https://www.netgate.com)
|
||||||
- [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`)
|
* [CloudMon360](http://cloudmon360.com)
|
||||||
- [Routing example](example/README.md)
|
* [Grideye](http://hagsand.se/grideye)
|
||||||
- [Clicon and Clixon project page](http://www.clicon.org)
|
* [Netclean](https://www.netclean.com/solutions/whitebox) (only CLIgen)
|
||||||
- [Tests](test/README.md)
|
* [Prosilient's PTAnalyzer]<a href="http://www.prosilient.com"> (only CLIgen)
|
||||||
|
|
||||||
|
See also [Clicon project page](http://clicon.org).
|
||||||
|
|
||||||
Installation
|
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
|
One [example application](example/README.md) is provided, a IETF IP YANG datamodel with
|
||||||
generated CLI and configuration interface.
|
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
|
Dependencies
|
||||||
============
|
============
|
||||||
Clixon depends on the following software packages, which need to exist on the target machine.
|
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
|
- Yacc/bison
|
||||||
- Lex/Flex
|
- Lex/Flex
|
||||||
- Fcgi (if restconf is enabled)
|
- 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?)
|
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
|
Clixon restconf is a daemon based on FASTCGI. Instructions are available to
|
||||||
General Public License Version 2; you choose.
|
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
|
Datastore
|
||||||
==========
|
=========
|
||||||
We implemented Clixon since we needed a generic configuration tool in
|
The Clixon datastore is a stand-alone XML based datastore. The idea is
|
||||||
several projects, including
|
to be able to use different datastores backends with the same
|
||||||
[KTH](http://www.csc.kth.se/~olofh/10G_OSR). Most of these projects
|
API.
|
||||||
were for embedded network and measuring-probe devices. We started with
|
|
||||||
something called Clicon which was based on a key-value specification
|
Update: There used to be a key-value plugin based on qdbm but isnow obsoleted. Only a text datastore is implemented.
|
||||||
and data-store. But as time passed new standards evolved and we
|
|
||||||
started adapting it to XML, Yang and netconf. Finally we made Clixon,
|
The datastore is primarily designed to be used by Clixon but can be used
|
||||||
where the legacy key specification has been replaced completely by
|
separately.
|
||||||
YANG and using XML as configuration data. This means that legacy
|
|
||||||
Clicon applications do not run on Clixon.
|
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
|
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.
|
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@
|
libdir = @libdir@
|
||||||
mandir = @mandir@
|
mandir = @mandir@
|
||||||
libexecdir = @libexecdir@
|
libexecdir = @libexecdir@
|
||||||
|
wwwdir = /www-data
|
||||||
localstatedir = @localstatedir@
|
localstatedir = @localstatedir@
|
||||||
sysconfdir = @sysconfdir@
|
sysconfdir = @sysconfdir@
|
||||||
includedir = @includedir@
|
includedir = @includedir@
|
||||||
|
|
|
||||||
|
|
@ -201,7 +201,6 @@ netconf_input_cb(int s,
|
||||||
retval = 0;
|
retval = 0;
|
||||||
goto done;
|
goto done;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i=0; i<len; i++){
|
for (i=0; i<len; i++){
|
||||||
if (buf[i] == 0)
|
if (buf[i] == 0)
|
||||||
continue; /* Skip NULL chars (eg from terminals) */
|
continue; /* Skip NULL chars (eg from terminals) */
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,17 @@ CFLAGS = @CFLAGS@
|
||||||
LDFLAGS = @LDFLAGS@
|
LDFLAGS = @LDFLAGS@
|
||||||
|
|
||||||
prefix = @prefix@
|
prefix = @prefix@
|
||||||
|
datarootdir = @datarootdir@
|
||||||
exec_prefix = @exec_prefix@
|
exec_prefix = @exec_prefix@
|
||||||
bindir = @bindir@
|
bindir = @bindir@
|
||||||
wwwdir = /www-data
|
libdir = @libdir@
|
||||||
|
mandir = @mandir@
|
||||||
libexecdir = @libexecdir@
|
libexecdir = @libexecdir@
|
||||||
|
wwwdir = /www-data
|
||||||
localstatedir = @localstatedir@
|
localstatedir = @localstatedir@
|
||||||
sysconfdir = @sysconfdir@
|
sysconfdir = @sysconfdir@
|
||||||
|
includedir = @includedir@
|
||||||
|
HOST_VENDOR = @host_vendor@
|
||||||
|
|
||||||
SH_SUFFIX = @SH_SUFFIX@
|
SH_SUFFIX = @SH_SUFFIX@
|
||||||
CLIXON_MAJOR = @CLIXON_VERSION_MAJOR@
|
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@
|
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)
|
OBJS = $(SRC:.c=.o)
|
||||||
|
|
||||||
|
|
@ -70,10 +74,18 @@ APPSRC = restconf_main.c
|
||||||
APPOBJ = $(APPSRC:.c=.o)
|
APPOBJ = $(APPSRC:.c=.o)
|
||||||
APPL = clixon_restconf
|
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:
|
clean:
|
||||||
rm -f $(OBJS) *.core $(APPL) $(APPOBJ)
|
rm -f $(OBJS) $(LIBOBJS) *.core $(APPL) $(APPOBJ) $(MYLIB) $(MYLIBSO) $(MYLIBLINK)
|
||||||
|
|
||||||
distclean: clean
|
distclean: clean
|
||||||
rm -f Makefile *~ .depend
|
rm -f Makefile *~ .depend
|
||||||
|
|
@ -82,14 +94,23 @@ distclean: clean
|
||||||
# Put other executables in libexec/
|
# Put other executables in libexec/
|
||||||
# Also create a libexec/ directory for writeable/temporary files.
|
# Also create a libexec/ directory for writeable/temporary files.
|
||||||
# Put config file in etc/
|
# Put config file in etc/
|
||||||
install: $(APPL)
|
install: install-lib $(APPL)
|
||||||
install -d $(DESTDIR)$(wwwdir)
|
install -d $(DESTDIR)$(wwwdir)
|
||||||
install $(APPL) $(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:
|
uninstall:
|
||||||
rm -f $(DESTDIR)$(wwwdir)/$(APPL)
|
rm -f $(DESTDIR)$(wwwdir)/$(APPL)
|
||||||
|
rm -f $(DESTDIR)$(libdir)/$(MYLIBLINK)*
|
||||||
|
|
||||||
.SUFFIXES:
|
.SUFFIXES:
|
||||||
.SUFFIXES: .c .o
|
.SUFFIXES: .c .o
|
||||||
|
|
@ -97,8 +118,18 @@ uninstall:
|
||||||
.c.o:
|
.c.o:
|
||||||
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $<
|
$(CC) $(INCLUDES) -D__PROGRAM__=\"$(APPL)\" $(CPPFLAGS) $(CFLAGS) -c $<
|
||||||
|
|
||||||
$(APPL) : $(APPOBJ) $(OBJS) $(LIBDEPS)
|
$(APPL) : $(APPOBJ) $(MYLIBLINK) $(OBJS) $(LIBDEPS)
|
||||||
$(CC) $(LDFLAGS) $(APPOBJ) $(OBJS) $(LIBS) -o $@
|
$(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:
|
TAGS:
|
||||||
find . -name '*.[chyl]' -print | etags -
|
find . -name '*.[chyl]' -print | etags -
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,4 @@
|
||||||
# Clixon Restconf
|
# 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
|
### 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, "HTTPS", dbg);
|
||||||
printparam(r, "HTTP_ACCEPT", dbg);
|
printparam(r, "HTTP_ACCEPT", dbg);
|
||||||
printparam(r, "HTTP_CONTENT_TYPE", dbg);
|
printparam(r, "HTTP_CONTENT_TYPE", dbg);
|
||||||
|
printparam(r, "HTTP_AUTHORIZATION", dbg);
|
||||||
#if 0 /* For debug */
|
#if 0 /* For debug */
|
||||||
clicon_debug(1, "All environment vars:");
|
clicon_debug(1, "All environment vars:");
|
||||||
{
|
{
|
||||||
|
|
@ -375,6 +376,7 @@ restconf_plugin_load(clicon_handle h)
|
||||||
plghndl_t *handle;
|
plghndl_t *handle;
|
||||||
char filename[MAXPATHLEN];
|
char filename[MAXPATHLEN];
|
||||||
|
|
||||||
|
clicon_debug(1, "%s", __FUNCTION__);
|
||||||
if ((dir = clicon_restconf_dir(h)) == NULL){
|
if ((dir = clicon_restconf_dir(h)) == NULL){
|
||||||
retval = 0;
|
retval = 0;
|
||||||
goto quit;
|
goto quit;
|
||||||
|
|
@ -382,7 +384,6 @@ restconf_plugin_load(clicon_handle h)
|
||||||
/* Get plugin objects names from plugin directory */
|
/* Get plugin objects names from plugin directory */
|
||||||
if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0)
|
if((ndp = clicon_file_dirent(dir, &dp, "(.so)$", S_IFREG))<0)
|
||||||
goto quit;
|
goto quit;
|
||||||
|
|
||||||
/* Load all plugins */
|
/* Load all plugins */
|
||||||
for (i = 0; i < ndp; i++) {
|
for (i = 0; i < ndp; i++) {
|
||||||
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
|
snprintf(filename, MAXPATHLEN-1, "%s/%s", dir, dp[i].d_name);
|
||||||
|
|
|
||||||
20
doc/FAQ.md
20
doc/FAQ.md
|
|
@ -303,4 +303,22 @@ fib_route(clicon_handle h, /* Clicon handle */
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Here, the callback is over-simplified.
|
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
|
BE_PLUGIN = $(APPNAME)_backend.so
|
||||||
CLI_PLUGIN = $(APPNAME)_cli.so
|
CLI_PLUGIN = $(APPNAME)_cli.so
|
||||||
NETCONF_PLUGIN = $(APPNAME)_netconf.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)
|
all: $(PLUGINS)
|
||||||
|
|
||||||
|
|
@ -73,22 +74,28 @@ YANGSPECS += example.yang
|
||||||
BE_SRC = routing_backend.c
|
BE_SRC = routing_backend.c
|
||||||
BE_OBJ = $(BE_SRC:%.c=%.o)
|
BE_OBJ = $(BE_SRC:%.c=%.o)
|
||||||
$(BE_PLUGIN): $(BE_OBJ)
|
$(BE_PLUGIN): $(BE_OBJ)
|
||||||
$(CC) -shared -o $@ -lc $<
|
$(CC) -Wall -shared -o $@ -lc $<
|
||||||
|
|
||||||
# CLI frontend plugin
|
# CLI frontend plugin
|
||||||
CLI_SRC = routing_cli.c
|
CLI_SRC = routing_cli.c
|
||||||
CLI_OBJ = $(CLI_SRC:%.c=%.o)
|
CLI_OBJ = $(CLI_SRC:%.c=%.o)
|
||||||
$(CLI_PLUGIN): $(CLI_OBJ)
|
$(CLI_PLUGIN): $(CLI_OBJ)
|
||||||
$(CC) -shared -o $@ -lc $^
|
$(CC) -Wall -shared -o $@ -lc $^
|
||||||
|
|
||||||
# NETCONF frontend plugin
|
# NETCONF frontend plugin
|
||||||
NETCONF_SRC = routing_netconf.c
|
NETCONF_SRC = routing_netconf.c
|
||||||
NETCONF_OBJ = $(NETCONF_SRC:%.c=%.o)
|
NETCONF_OBJ = $(NETCONF_SRC:%.c=%.o)
|
||||||
$(NETCONF_PLUGIN): $(NETCONF_OBJ)
|
$(NETCONF_PLUGIN): $(NETCONF_OBJ)
|
||||||
$(CC) -shared -o $@ -lc $^
|
$(CC) -Wall -shared -o $@ -lc $^
|
||||||
|
|
||||||
SRC = $(BE_SRC) $(CLI_SRC) $(NETCONF_SRC)
|
# RESTCONF frontend plugin
|
||||||
OBJS = $(BE_OBJ) $(CLI_OBJ) $(NETCONF_OBJ)
|
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:
|
clean:
|
||||||
rm -f $(PLUGINS) $(OBJS)
|
rm -f $(PLUGINS) $(OBJS)
|
||||||
|
|
@ -98,7 +105,7 @@ distclean: clean
|
||||||
rm -f Makefile *~ .depend
|
rm -f Makefile *~ .depend
|
||||||
(cd docker && $(MAKE) $(MFLAGS) $@)
|
(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 -d $(DESTDIR)$(clixon_SYSCONFDIR)
|
||||||
install $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR)
|
install $(APPNAME).xml $(DESTDIR)$(clixon_SYSCONFDIR)
|
||||||
install -d $(DESTDIR)$(clixon_DBSPECDIR)/yang
|
install -d $(DESTDIR)$(clixon_DBSPECDIR)/yang
|
||||||
|
|
@ -108,7 +115,9 @@ install: $(YANGSPECS) $(CLISPECS) $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN) $
|
||||||
install -d $(DESTDIR)$(clixon_LIBDIR)/backend
|
install -d $(DESTDIR)$(clixon_LIBDIR)/backend
|
||||||
install $(BE_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend;
|
install $(BE_PLUGIN) $(DESTDIR)$(clixon_LIBDIR)/backend;
|
||||||
install -d $(DESTDIR)$(clixon_LIBDIR)/netconf
|
install -d $(DESTDIR)$(clixon_LIBDIR)/netconf
|
||||||
install $(NETCONF_PLUGIN) $(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 -d $(DESTDIR)$(clixon_LIBDIR)/clispec
|
||||||
install $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec;
|
install $(CLISPECS) $(DESTDIR)$(clixon_LIBDIR)/clispec;
|
||||||
install -d $(DESTDIR)$(clixon_LOCALSTATEDIR)
|
install -d $(DESTDIR)$(clixon_LOCALSTATEDIR)
|
||||||
|
|
|
||||||
|
|
@ -11,4 +11,21 @@ module example {
|
||||||
}
|
}
|
||||||
description
|
description
|
||||||
"Example code that includes ietf-ip and ietf-routing";
|
"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 */
|
/* Find plugin by name callback. XXX Should be clicon internal */
|
||||||
typedef void *(find_plugin_t)(clicon_handle, char *);
|
typedef void *(find_plugin_t)(clicon_handle, char *);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Prototypes
|
* Prototypes
|
||||||
*/
|
*/
|
||||||
|
|
@ -60,7 +64,7 @@ typedef void *(find_plugin_t)(clicon_handle, char *);
|
||||||
* @see plginit_t
|
* @see plginit_t
|
||||||
*/
|
*/
|
||||||
#define PLUGIN_INIT "plugin_init"
|
#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.
|
/* Called when backend started with cmd-line arguments from daemon call.
|
||||||
* @see plgstart_t
|
* @see plgstart_t
|
||||||
|
|
@ -86,6 +90,18 @@ typedef int (plgexit_t)(clicon_handle); /* Plugin exit */
|
||||||
*/
|
*/
|
||||||
typedef int (plgcredentials_t)(clicon_handle, void *, char **username);
|
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 */
|
/* Find a function in global namespace or a plugin. XXX clicon internal */
|
||||||
void *clicon_find_func(clicon_handle h, char *plugin, char *func);
|
void *clicon_find_func(clicon_handle h, char *plugin, char *func);
|
||||||
|
|
||||||
|
|
|
||||||
39
test/lib.sh
39
test/lib.sh
|
|
@ -28,13 +28,17 @@ rm -rf $dir/*
|
||||||
|
|
||||||
# error and exit, arg is optional extra errmsg
|
# error and exit, arg is optional extra errmsg
|
||||||
err(){
|
err(){
|
||||||
echo "Error in Test$testnr [$testname]:"
|
echo -e "\e[31m\nError in Test$testnr [$testname]:"
|
||||||
if [ $# -gt 0 ]; then
|
if [ $# -gt 0 ]; then
|
||||||
echo "Expected: $1"
|
echo "Expected: $1"
|
||||||
fi
|
fi
|
||||||
if [ $# -gt 1 ]; then
|
if [ $# -gt 1 ]; then
|
||||||
echo "Received: $2"
|
echo "Received: $2"
|
||||||
fi
|
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
|
exit $testnr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +47,11 @@ new(){
|
||||||
testnr=`expr $testnr + 1`
|
testnr=`expr $testnr + 1`
|
||||||
testname=$1
|
testname=$1
|
||||||
>&2 echo "Test$testnr [$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
|
# clixon tester. First arg is command and second is expected outcome
|
||||||
|
|
@ -82,34 +90,15 @@ expectfn(){
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Similar to expectfn, but checks for equality and not only match
|
expecteq(){
|
||||||
expecteq2(){
|
|
||||||
ret=$1
|
ret=$1
|
||||||
expect=$2
|
expect=$2
|
||||||
|
|
||||||
# Match if both are empty string
|
|
||||||
if [ -z "$ret" -a -z "$expect" ]; then
|
if [ -z "$ret" -a -z "$expect" ]; then
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
if [ "$ret" != "$expect" ]; then
|
if [[ "$ret" = "$expect" ]]; then
|
||||||
err "$expect" "$ret"
|
echo
|
||||||
fi
|
else
|
||||||
}
|
|
||||||
|
|
||||||
# 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
|
|
||||||
err "$expect" "$ret"
|
err "$expect" "$ret"
|
||||||
fi
|
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 tests"
|
||||||
|
|
||||||
new "restconf root discovery. RFC 8040 3.1 (xml+xrd)"
|
new2 "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'>
|
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'/>
|
<Link rel='restconf' href='/restconf'/>
|
||||||
</XRD>
"
|
</XRD>
"
|
||||||
|
|
||||||
new "restconf get restconf resource. RFC 8040 3.3 (json)"
|
new2 "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"}}
|
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?
|
# 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)"
|
new2 "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}}
|
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)"
|
new "restconf get restconf/operations. RFC8040 3.3.2 (xml)"
|
||||||
|
|
@ -104,8 +104,8 @@ if [ -z "$match" ]; then
|
||||||
err "$expect" "$ret"
|
err "$expect" "$ret"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf get restconf/yang-library-version. RFC8040 3.3.3"
|
new2 "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"}'
|
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)"
|
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)
|
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"
|
expectfn "curl -s -I http://localhost/restconf/data" "HTTP/1.1 200 OK"
|
||||||
#Content-Type: application/yang-data+json"
|
#Content-Type: application/yang-data+json"
|
||||||
|
|
||||||
new "restconf empty rpc"
|
new2 "restconf empty rpc"
|
||||||
expecteq2 "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null}
|
expecteq "$(curl -s -X POST -d {\"input\":{\"name\":\"\"}} http://localhost/restconf/operations/ex:empty)" '{"output": null}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf get empty config + state json"
|
new2 "restconf get empty config + state json"
|
||||||
expecteq2 "$(curl -sSG http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
|
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"
|
new "restconf get empty config + state xml"
|
||||||
|
|
@ -138,8 +138,8 @@ if [ -z "$match" ]; then
|
||||||
err "$expect" "$ret"
|
err "$expect" "$ret"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf get data/interfaces-state/interface=eth0 json"
|
new2 "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}]}
|
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"
|
new "restconf get state operation eth0 xml"
|
||||||
|
|
@ -151,8 +151,8 @@ if [ -z "$match" ]; then
|
||||||
err "$expect" "$ret"
|
err "$expect" "$ret"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf get state operation eth0 type json"
|
new2 "restconf get state operation eth0 type json"
|
||||||
expecteq2 "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"}
|
expecteq "$(curl -s -G http://localhost/restconf/data/interfaces-state/interface=eth0/type)" '{"type": "eth"}
|
||||||
'
|
'
|
||||||
|
|
||||||
new "restconf get state operation eth0 type xml"
|
new "restconf get state operation eth0 type xml"
|
||||||
|
|
@ -164,8 +164,8 @@ if [ -z "$match" ]; then
|
||||||
err "$expect" "$ret"
|
err "$expect" "$ret"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
new "restconf GET datastore"
|
new2 "restconf GET datastore"
|
||||||
expecteq2 "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
|
expecteq "$(curl -s -X GET http://localhost/restconf/data)" '{"data": {"interfaces-state": {"interface": [{"name": "eth0","type": "eth","if-index": 42}]}}}
|
||||||
'
|
'
|
||||||
|
|
||||||
# Exact match
|
# 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"}}}'
|
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
|
# 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"
|
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}\]}}
|
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"
|
new2 "restconf delete interfaces"
|
||||||
expecteq2 $(curl -s -X DELETE http://localhost/restconf/data/interfaces) ""
|
expecteq $(curl -s -X DELETE http://localhost/restconf/data/interfaces) ""
|
||||||
|
|
||||||
new "restconf Check empty config"
|
new "restconf Check empty config"
|
||||||
expectfn "curl -sG http://localhost/restconf/data" "$state"
|
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"
|
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' ""
|
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
|
# 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"
|
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}]}}}
|
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"
|
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"}}}
'
|
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"
|
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' ""
|
expecteq "$(curl -s -X POST -d '{"description":"The-first-interface"}' http://localhost/restconf/data/interfaces/interface=eth%2f0%2f0)" ""
|
||||||
|
|
||||||
new "Add nothing using POST"
|
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"
|
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"
|
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}]}}}
|
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"
|
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"
|
new "Check deleted eth/0/0"
|
||||||
expectfn 'curl -s -G http://localhost/restconf/data' $state
|
expectfn 'curl -s -G http://localhost/restconf/data' $state
|
||||||
|
|
||||||
new "restconf Re-Delete eth/0/0 using none should generate error"
|
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"}}}
'
|
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"
|
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' ""
|
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"
|
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}]}}}
|
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"
|
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"}}}}
|
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"
|
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"
|
new "restconf POST interface"
|
||||||
expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' ""
|
expectfn 'curl -s -X POST -d {"interface":{"name":"TEST","type":"eth0"}} http://localhost/restconf/data/cont1' ""
|
||||||
|
|
||||||
new "restconf POST again"
|
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"}}}
'
|
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"
|
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"}}}
'
|
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"
|
new "restconf DELETE"
|
||||||
expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' ""
|
expectfn 'curl -s -X DELETE http://localhost/restconf/data/cont1' ""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue