Fixed ssl client certs for evhtp.

* Added SSL cert info as options: CLICON_SSL_SERVER_CERT, CLICON_SSL_SERVER_KEY, CLICON_SSL_CA_CERT
Added config.sh for testing for autotools
This commit is contained in:
Olof hagsand 2020-07-01 15:11:22 +02:00
parent 9c82e97072
commit c049a397b0
22 changed files with 515 additions and 80 deletions

1
.gitignore vendored
View file

@ -58,6 +58,7 @@ util/clixon_util_xml
util/clixon_util_xpath
util/clixon_util_yang
test/config.sh
test/site.sh
test/vagrant/site.mk
test/cicd/site.mk

View file

@ -33,7 +33,7 @@ Expected: July 2020
* Added a prefix for cli_show_config/cli_show_auto so that it can produce parseable output
* Thanks dcornejo@netgate.com for trying it out and suggestions
* Embedding restconf into the existing [libevhtp](https://github.com/criticalstack/libevhtp) embedded web server. (Experimental).
* The existing FCGI restconf solution will continue to be supported for NGINX and other reverese proxies with an fast CGI API.
* The existing FCGI restconf solution will continue to be supported for NGINX and other reverse proxies with a FCGI API.
* The restconf code has been refactored to support both modes. Hopefully, it should be straightforward to add another embedded server, such as GNU microhttpd.
* The new restconf module is selected using a compile-time autotools configure as follows:
* `--with-restconf=fcgi FCGI interface for stand-alone web rev-proxy eg nginx (default)`
@ -45,6 +45,11 @@ Expected: July 2020
* New clixon-config@2020-06-17.yang revision
* Added CLICON_CLI_LINES_DEFAULT for setting window row size of raw terminals
* Added enum HIDE to CLICON_CLI_GENMODEL for auto-cli
* Added SSL cert info for evhtp restconf https:
* CLICON_SSL_SERVER_CERT
* CLICON_SSL_SERVER_KEY
* CLICON_SSL_CA_CERT
* Restconf FCGI (eg via nginx) have changed reply message syntax slightly as follows (due to refactoring and common code with evhtp):
* Bodies in error reyruns including html code have been removed
* Some (extra) CRLF:s have been removed

View file

@ -35,9 +35,6 @@
* libevhtp code
*/
/* XXX temp constant should go away, */
#undef _EVHTP_NYI
#ifdef HAVE_CONFIG_H
#include "clixon_config.h" /* generated by config & autoconf */
#endif
@ -82,7 +79,7 @@
#include "restconf_root.h"
/* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:P:s"
#define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:P:sc"
/* Need global variable to for signal handler XXX */
static clicon_handle _CLICON_HANDLE = NULL;
@ -100,9 +97,7 @@ restconf_sig_term(int arg)
else
exit(-1);
if (_CLICON_HANDLE){
#ifdef _EVHTP_NYI
stream_child_freeall(_CLICON_HANDLE);
#endif
// stream_child_freeall(_CLICON_HANDLE);
restconf_terminate(_CLICON_HANDLE);
}
clicon_exit_set(); /* checked in clixon_event_loop() */
@ -116,9 +111,6 @@ restconf_sig_child(int arg)
int pid;
if ((pid = waitpid(-1, &status, 0)) != -1 && WIFEXITED(status)){
#ifdef _EVHTP_NYI
;
#endif
}
}
@ -267,10 +259,15 @@ evhtp_params_set(clicon_handle h,
evhtp_request_t *req,
cvec *qvec)
{
int retval = -1;
htp_method meth;
evhtp_uri_t *uri;
evhtp_path_t *path;
int retval = -1;
htp_method meth;
evhtp_uri_t *uri;
evhtp_path_t *path;
evhtp_ssl_t *ssl = NULL;
char *subject;
cvec *cvv = NULL;
char *cn;
if ((uri = req->uri) == NULL){
clicon_err(OE_DAEMON, EFAULT, "No uri");
@ -303,9 +300,18 @@ evhtp_params_set(clicon_handle h,
goto fail;
}
clicon_debug(1, "%s conn->ssl:%d", __FUNCTION__, req->conn->ssl?1:0);
if (req->conn->ssl != NULL){
if ((ssl = req->conn->ssl) != NULL){
if (restconf_param_set(h, "HTTPS", "https") < 0) /* some string or NULL */
goto done;
/* SSL subject fields, eg CN (Common Name) , can add more here? */
if ((subject = (char*)htp_sslutil_subject_tostr(req->conn->ssl)) != NULL){
if (str2cvec(subject, '/', '=', &cvv) < 0)
goto done;
if ((cn = cvec_find_str(cvv, "CN")) != NULL){
if (restconf_param_set(h, "SSL_CN", cn) < 0)
goto done;
}
}
}
/* Translate all http headers by capitalizing, prepend w HTTP_ and - -> _
@ -315,6 +321,8 @@ evhtp_params_set(clicon_handle h,
goto done;
retval = 1;
done:
if (cvv)
cvec_free(cvv);
return retval;
fail:
retval = 0;
@ -423,6 +431,8 @@ cx_path_restconf(evhtp_request_t *req,
/* input debug */
if (clicon_debug_get())
evhtp_headers_for_each(req->headers_in, print_header, h);
/* get accepted connection */
/* Query vector, ie the ?a=x&b=y stuff */
if ((qvec = cvec_new(0)) ==NULL){
@ -446,51 +456,93 @@ cx_path_restconf(evhtp_request_t *req,
}
/*! Get Server cert info
* @param[out] ssl_config
* @param[in] h Clicon handle
* @param[out] ssl_config evhtp ssl config struct
*/
static int
get_servercerts(evhtp_ssl_cfg_t *ssl_config,
const char *pki_dir,
const char *ssl_server_cert)
cx_get_certs(clicon_handle h,
int ssl_verify_clients,
evhtp_ssl_cfg_t *ssl_config)
{
int retval = -1;
cbuf *cb = NULL;
struct stat f_stat;
char *filename;
if (ssl_config == NULL){
clicon_err(OE_CFG, EINVAL, "ssl_config is NULL");
clicon_err(OE_CFG, EINVAL, "Input parameter is NULL");
goto done;
}
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
if ((filename = clicon_option_str(h, "CLICON_SSL_SERVER_CERT")) == NULL){
clicon_err(OE_CFG, EFAULT, "CLICON_SSL_SERVER_CERT option missing");
goto done;
}
cprintf(cb, "%s/%s-crt.pem", pki_dir, ssl_server_cert);
if ((ssl_config->pemfile = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
if ((ssl_config->pemfile = strdup(filename)) == NULL){
clicon_err(OE_CFG, errno, "strdup");
goto done;
}
if (stat(ssl_config->pemfile, &f_stat) != 0) {
clicon_err(OE_FATAL, errno, "Cannot load SSL cert '%s'", ssl_config->pemfile);
goto done;
}
cbuf_reset(cb);
cprintf(cb, "%s/%s-key.pem", pki_dir, ssl_server_cert);
if ((ssl_config->privfile = strdup(cbuf_get(cb))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
if ((filename = clicon_option_str(h, "CLICON_SSL_SERVER_KEY")) == NULL){
clicon_err(OE_CFG, EFAULT, "CLICON_SSL_SERVER_KEY option missing");
goto done;
}
if ((ssl_config->privfile = strdup(filename)) == NULL){
clicon_err(OE_CFG, errno, "strdup");
goto done;
}
if (stat(ssl_config->privfile, &f_stat) != 0) {
clicon_err(OE_FATAL, errno, "Cannot load SSL key '%s'", ssl_config->privfile);
goto done;
}
if (ssl_verify_clients){
if ((filename = clicon_option_str(h, "CLICON_SSL_CA_CERT")) == NULL){
clicon_err(OE_CFG, EFAULT, "CLICON_SSL_CA_CERT option missing");
goto done;
}
if ((ssl_config->cafile = strdup(filename)) == NULL){
clicon_err(OE_CFG, errno, "strdup");
goto done;
}
if (stat(ssl_config->cafile, &f_stat) != 0) {
clicon_err(OE_FATAL, errno, "Cannot load SSL key '%s'", ssl_config->privfile);
goto done;
}
}
retval = 0;
done:
if (cb)
cbuf_free(cb);
return retval;
}
static int
cx_verify_certs(int pre_verify,
evhtp_x509_store_ctx_t *store)
{
#ifdef NOTYET
char buf[256];
X509 * err_cert;
int err;
int depth;
SSL * ssl;
evhtp_connection_t * connection;
evhtp_ssl_cfg_t * ssl_cfg;
fprintf(stderr, "%s %d\n", __FUNCTION__, pre_verify);
err_cert = X509_STORE_CTX_get_current_cert(store);
err = X509_STORE_CTX_get_error(store);
depth = X509_STORE_CTX_get_error_depth(store);
ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx());
X509_NAME_oneline(X509_get_subject_name(err_cert), buf, 256);
connection = SSL_get_app_data(ssl);
ssl_cfg = connection->htp->ssl_cfg;
#endif
return pre_verify;
}
/*! Usage help routine
* @param[in] argv0 command line
* @param[in] h Clicon handle
@ -513,6 +565,7 @@ usage(clicon_handle h,
"\t-u <path|addr>\t Internal socket domain path or IP addr (see -a)\n"
"\t-o \"<option>=<value>\" Give configuration option overriding config file (see clixon-config.yang)\n"
"\t-s\t SSL server, https\n"
"\t-c\t SSL verify client certs\n"
"\t-P <port>\t HTTP port (default 80, or 443 if -s is given)\n"
,
argv0,
@ -521,6 +574,8 @@ usage(clicon_handle h,
exit(0);
}
/*! Main routine for libevhtp restconf
*/
int
@ -541,15 +596,12 @@ main(int argc,
size_t cligen_bufthreshold;
uint16_t defaultport = 80;
uint16_t port = 0;
#ifdef _EVHTP_NYI
char *stream_path;
#endif
evhtp_t *htp = NULL;
struct event_base *evbase = NULL;
evhtp_ssl_cfg_t *ssl_config = NULL;
int dbg = 0;
char *ssl_server = NULL; /* SSL server name, default "server", base for srv certs */
char *pki_dir = CLIXON_PKI_DIR;
int use_ssl = 0;
int ssl_verify_clients = 0;
/* In the startup, logs to stderr & debug flag set later */
clicon_log_init(__PROGRAM__, LOG_INFO, logdst);
@ -607,9 +659,7 @@ main(int argc,
/* Find and read configfile */
if (clicon_options_main(h) < 0)
goto done;
#ifdef _EVHTP_NYI
stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
#endif
// stream_path = clicon_option_str(h, "CLICON_STREAM_PATH");
/* Now rest of options, some overwrite option file */
optind = 1;
@ -651,9 +701,12 @@ main(int argc,
break;
}
case 's': /* ssl: use https */
ssl_server = CLIXON_SERVER_CERT;
use_ssl = 1;
defaultport = 443; /* unless explicit -P ? */
break;
case 'c': /* ssl: verify clients */
ssl_verify_clients = 1;
break;
case 'P': /* http port */
if (!strlen(optarg))
usage(h, argv0);
@ -669,7 +722,7 @@ main(int argc,
if (port == 0)
port = defaultport;
/* Check server ssl certs */
if (ssl_server){
if (use_ssl){
/* Init evhtp ssl config struct */
if ((ssl_config = malloc(sizeof(evhtp_ssl_cfg_t))) == NULL){
clicon_err(OE_UNIX, errno, "malloc");
@ -678,8 +731,14 @@ main(int argc,
memset(ssl_config, 0, sizeof(evhtp_ssl_cfg_t));
ssl_config->ssl_opts = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1;
if (get_servercerts(ssl_config, pki_dir, ssl_server) < 0)
if (cx_get_certs(h, ssl_verify_clients, ssl_config) < 0)
goto done;
ssl_config->x509_verify_cb = cx_verify_certs; /* Is extra verification necessary? */
if (ssl_verify_clients){
ssl_config->verify_peer = SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
ssl_config->x509_verify_cb = cx_verify_certs;
ssl_config->verify_depth = 2;
}
}
// ssl_verify_mode = htp_sslutil_verify2opts(optarg);
@ -697,7 +756,7 @@ main(int argc,
clicon_err(OE_UNIX, errno, "evhtp_new");
goto done;
}
if (ssl_server){
if (use_ssl){
if (evhtp_ssl_init(htp, ssl_config) < 0){
clicon_err(OE_UNIX, errno, "evhtp_new");
goto done;
@ -835,9 +894,7 @@ main(int argc,
retval = 0;
done:
#ifdef _EVHTP_NYI
stream_child_freeall(h);
#endif
// stream_child_freeall(h);
restconf_terminate(h);
return retval;
}

3
configure vendored
View file

@ -5326,7 +5326,7 @@ _ACEOF
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/main/Makefile extras/rpm/Makefile docker/Makefile docker/main/Makefile docker/base/Makefile util/Makefile yang/Makefile yang/clixon/Makefile yang/mandatory/Makefile yang/optional/Makefile doc/Makefile test/Makefile test/cicd/Makefile test/vagrant/Makefile"
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/main/Makefile extras/rpm/Makefile docker/Makefile docker/main/Makefile docker/base/Makefile util/Makefile yang/Makefile yang/clixon/Makefile yang/mandatory/Makefile yang/optional/Makefile doc/Makefile test/Makefile test/config.sh test/cicd/Makefile test/vagrant/Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@ -6045,6 +6045,7 @@ do
"yang/optional/Makefile") CONFIG_FILES="$CONFIG_FILES yang/optional/Makefile" ;;
"doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;;
"test/Makefile") CONFIG_FILES="$CONFIG_FILES test/Makefile" ;;
"test/config.sh") CONFIG_FILES="$CONFIG_FILES test/config.sh" ;;
"test/cicd/Makefile") CONFIG_FILES="$CONFIG_FILES test/cicd/Makefile" ;;
"test/vagrant/Makefile") CONFIG_FILES="$CONFIG_FILES test/vagrant/Makefile" ;;

View file

@ -326,6 +326,7 @@ AC_OUTPUT(Makefile
yang/optional/Makefile
doc/Makefile
test/Makefile
test/config.sh
test/cicd/Makefile
test/vagrant/Makefile
)

View file

@ -14,6 +14,8 @@
* [Thanks](#thanks)
* [References](#references)
NOTE: Outdated docs, see: https://clixon-docs.readthedocs.io for updated docs
## Background
This document describes the configuration startup mechanism of the Clixon backend. It describes the mechanism of Clixon version 3.10 which supports the following features:
@ -334,7 +336,7 @@ where changes to the Yang model are documented and loaded into
Clixon. The implementation is not complete.
When upgrading, the system parses the changelog and tries to upgrade
the datastore automatically. This featire is experimental and has
the datastore automatically. This feature is experimental and has
several limitations.
You enable the automatic upgrading by registering the changelog upgrade method in `clixon_plugin_ini()` using wildcards:

View file

@ -64,6 +64,9 @@
/* These include signatures for plugin and transaction callbacks. */
#include <clixon/clixon_backend.h>
/* Command line options to be passed to getopt(3) */
#define BACKEND_EXAMPLE_OPTS "rsS:iuUt"
/*! Variable to control if reset code is run.
* The reset code inserts "extra XML" which assumes ietf-interfaces is
* loaded, and this is not always the case.
@ -1015,7 +1018,7 @@ clixon_plugin_init(clicon_handle h)
goto done;
opterr = 0;
optind = 1;
while ((c = getopt(argc, argv, "rsS:iuUt")) != -1)
while ((c = getopt(argc, argv, BACKEND_EXAMPLE_OPTS)) != -1)
switch (c) {
case 'r':
_reset = 1;

View file

@ -51,6 +51,12 @@
#include <clixon/clixon.h>
#include <clixon/clixon_restconf.h> /* minor use */
/* Command line options to be passed to getopt(3)
* -a basic authentication
* -s ssl client certificates
*/
#define RESTCONF_EXAMPLE_OPTS "as"
static const char Base64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char Pad64 = '=';
@ -60,6 +66,11 @@ static const char Pad64 = '=';
*/
static int basic_auth = 0;
/* Use https ssl client certs, map subject CN to user. Set by starting restonf with:
* clixon_restconf ... -- -s
*/
static int ssl_client_certs = 0;
/* 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.
@ -187,18 +198,17 @@ b64_decode(const char *src,
return (tarindex);
}
/*! Process a rest request that requires (cookie) "authentication"
/*! HTTP basic authentication example (note hardwired)
* @param[in] h Clixon handle
* @param[in] arg Argument. Here: Fastcgi request handle
* @retval -1 Fatal error
* @retval 0 Unauth
* @retval 1 Auth
* @note: Three hardwired users: andy, wilma, guest w password "bar".
* Enabled by passing -- -a to the main function
*/
int
example_restconf_credentials(clicon_handle h,
void *arg)
static int
example_basic_auth(clicon_handle h,
void *arg)
{
int retval = -1;
cxobj *xt = NULL;
@ -210,9 +220,6 @@ example_restconf_credentials(clicon_handle h,
size_t authlen;
int ret;
/* HTTP basic authentication not enabled, pass with user "none" */
if (basic_auth==0)
goto ok;
/* At this point in the code we must use HTTP basic authentication */
if ((auth = restconf_param_get(h, "HTTP_AUTHORIZATION")) == NULL)
goto fail;
@ -249,7 +256,7 @@ example_restconf_credentials(clicon_handle h,
clicon_debug(1, "%s user:%s", __FUNCTION__, user);
if (clicon_username_set(h, user) < 0)
goto done;
ok: /* authenticated */
/* authenticated */
retval = 1;
done: /* error */
clicon_debug(1, "%s retval:%d", __FUNCTION__, retval);
@ -265,6 +272,53 @@ example_restconf_credentials(clicon_handle h,
goto done;
}
/*! SSL client cert authentication.
* @param[in] h Clixon handle
* @param[in] arg
* @retval -1 Fatal error
* @retval 0 Unauth
* @retval 1 Auth
*/
static int
example_client_certs(clicon_handle h,
void *arg)
{
int retval = -1;
char *cn;
/* Check for cert subject common name (CN) */
if ((cn = restconf_param_get(h, "SSL_CN")) == NULL)
goto fail;
if (clicon_username_set(h, cn) < 0)
goto done;
/* authenticated */
retval = 1;
done: /* error */
return retval;
fail: /* unauthenticated */
retval = 0;
goto done;
}
/*! Authentication callback
* @param[in] h Clixon handle
* @param[in] arg
* @retval -1 Fatal error
* @retval 0 Unauth
* @retval 1 Auth
*/
int
example_restconf_credentials(clicon_handle h,
void *arg)
{
if (basic_auth)
return example_basic_auth(h, arg);
else if (ssl_client_certs)
return example_client_certs(h, arg);
else
return 1;
}
/*! Local example restconf rpc callback
*/
int
@ -338,11 +392,14 @@ clixon_plugin_init(clicon_handle h)
return NULL;
opterr = 0;
optind = 1;
while ((c = getopt(argc, argv, "a")) != -1)
while ((c = getopt(argc, argv, RESTCONF_EXAMPLE_OPTS)) != -1)
switch (c) {
case 'a':
case 'a': /* basic authentication */
basic_auth = 1;
break;
case 's': /* ssl client certs */
ssl_client_certs = 1;
break;
default:
break;
}

View file

@ -100,8 +100,3 @@
* clixon-4.4
*/
#define STATE_ORDERED_BY_SYSTEM
/*! Dir for Public Key Infrastructure (PKI) X.509 certificates
*/
#define CLIXON_SERVER_CERT "server"
#define CLIXON_PKI_DIR "/etc/pki/clixon"

View file

@ -97,3 +97,17 @@ For example, in FreeBSD, add:
make=gmake
```
## https
If you use evhtp with `configure --with-restconf=evhtp`, you can prepend the tests with RCPROTO=https which will run all restconf tests with SSL https and server certs.
Ensure the server keys are in order, as follows.
If you already have server certs, ensure CLICON_SSL_SERVER_CERT and CLICON_SSL_SERVER_KEY points to them.
If you do not have them, generate self-signed certs, eg as follows:
```
openssl req -x509 -nodes -newkey rsa:4096 -keyout /etc/ssl/private/clixon-server-key.pem -out /etc/ssl/certs/clixon-server-crt.pem -days 365
```
There are also client-cert tests, eg test_ssl*.sh

4
test/config.sh.in Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env bash
# Generated from autotools
WITH_RESTCONF=@with_restconf@

View file

@ -26,6 +26,14 @@ version=4
>&2 echo "Running $testfile"
# Generated config file from autotools / configure
if [ -f ./config.sh ]; then
. ./config.sh
if [ $? -ne 0 ]; then
return -1 # error
fi
fi
# Site file, an example of this file in README.md
if [ -f ./site.sh ]; then
. ./site.sh
@ -139,10 +147,11 @@ if [ ! -G $dir ]; then
sudo chgrp $u $dir
fi
# If you bring your own backend BE=0 (it is already started),the backend may
# If you bring your own backend BE=0 (it is already started), the backend may
# have created some files (eg unix socket) in $dir and therefore cannot
# be deleted
if [ $BE -ne 0 ]; then
# be deleted.
# Same with RC=0
if [ $BE -ne 0 -a $RC -ne 0 ]; then
rm -rf $dir/*
fi
@ -232,7 +241,7 @@ wait_backend(){
start_restconf(){
# Start in background
if [ $RCPROTO = https ]; then
EXTRA="-s"
EXTRA="-s" # server certs
else
EXTRA=
fi

View file

@ -31,7 +31,6 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

@ -62,7 +62,6 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

@ -42,7 +42,6 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

@ -34,7 +34,6 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

View file

@ -30,7 +30,6 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
</clixon-config>
EOF

View file

@ -48,7 +48,6 @@ cat <<EOF > $cfg
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_NACM_CREDENTIALS>none</CLICON_NACM_CREDENTIALS>
</clixon-config>
EOF

263
test/test_ssl_certs.sh Executable file
View file

@ -0,0 +1,263 @@
#!/usr/bin/env bash
# Restconf+NACM openssl functionality using server and client certs
# The test creates certs and keys:
# A CA, server key/cert, user key/cert for two users
# Can we try illegal certs?
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
# Only works with evhtp and https
if [ "${WITH_RESTCONF}" != "evhtp" -o "$RCPROTO" = http ]; then
if [ "$s" = $0 ]; then exit 0; else return 0; fi # skip
fi
APPNAME=example
# Common NACM scripts
. ./nacm.sh
# force it (or check it?)
RCPROTO=https
fyang=$dir/example.yang
cfg=$dir/conf.xml
certdir=$dir/certs
srvkey=$certdir/srv_key.pem
srvcert=$certdir/srv_cert.pem
cakey=$certdir/ca_key.pem # needed?
cacert=$certdir/ca_cert.pem
users="andy guest" # generate certs for some users in nacm.sh
# Whether to generate new keys or not (only if $dir is not removed)
# Here dont generate keys if restconf started stand-alone
genkeys=true
if [ $RC -eq 0 ]; then
genkeys=false
fi
test -d $certdir || mkdir $certdir
# Use yang in example
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_BACKEND_REGEXP>example_backend.so$</CLICON_BACKEND_REGEXP>
<CLICON_RESTCONF_DIR>/usr/local/lib/$APPNAME/restconf</CLICON_RESTCONF_DIR>
<CLICON_RESTCONF_PRETTY>false</CLICON_RESTCONF_PRETTY>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_MODULE_LIBRARY_RFC7895>true</CLICON_MODULE_LIBRARY_RFC7895>
<CLICON_NACM_MODE>internal</CLICON_NACM_MODE>
<CLICON_SSL_SERVER_CERT>$srvcert</CLICON_SSL_SERVER_CERT>
<CLICON_SSL_SERVER_KEY>$srvkey</CLICON_SSL_SERVER_KEY>
<CLICON_SSL_CA_CERT>$cacert</CLICON_SSL_CA_CERT>
</clixon-config>
EOF
cat <<EOF > $fyang
module example{
yang-version 1.1;
namespace "urn:example:example";
prefix ex;
import ietf-netconf-acm {
prefix nacm;
}
leaf x{
type int32;
description "something to edit";
}
}
EOF
# Two groups: admin allow all, guest allow nothing
RULES=$(cat <<EOF
<nacm xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-acm">
<enable-nacm>false</enable-nacm>
<read-default>permit</read-default>
<write-default>deny</write-default>
<exec-default>deny</exec-default>
$NGROUPS
<rule-list>
<name>guest-acl</name>
<group>guest</group>
<rule>
<name>deny-ncm</name>
<module-name>*</module-name>
<access-operations>*</access-operations>
<action>deny</action>
<comment>
Do not allow guests any access to the NETCONF
</comment>
</rule>
</rule-list>
$NADMIN
</nacm>
<x xmlns="urn:example:example">0</x>
EOF
)
if $genkeys; then
# Create certs
# 1. CA
cat<<EOF > $dir/ca.cnf
[ ca ]
default_ca = CA_default
[ CA_default ]
serial = ca-serial
crl = ca-crl.pem
database = ca-database.txt
name_opt = CA_default
cert_opt = CA_default
default_crl_days = 9999
default_md = md5
[ req ]
default_bits = 2048
days = 1
distinguished_name = req_distinguished_name
attributes = req_attributes
prompt = no
output_password = password
[ req_distinguished_name ]
C = SE
L = Stockholm
O = Clixon
OU = clixon
CN = ca
emailAddress = olof@hagsand.se
[ req_attributes ]
challengePassword = test
EOF
# Generate CA cert
openssl req -x509 -days 1 -config $dir/ca.cnf -keyout $cakey -out $cacert
cat<<EOF > $dir/srv.cnf
[req]
prompt = no
distinguished_name = dn
req_extensions = ext
[dn]
CN = www.clicon.org # localhost
emailAddress = olof@hagsand.se
O = Clixon
L = Stockholm
C = SE
[ext]
subjectAltName = DNS:clicon.org
EOF
# Generate server key
openssl genrsa -out $srvkey 2048
# Generate CSR (signing request)
openssl req -new -config $dir/srv.cnf -key $srvkey -out $certdir/srv_csr.pem
# Sign server cert by CA
openssl x509 -req -extfile $dir/srv.cnf -days 1 -passin "pass:password" -in $certdir/srv_csr.pem -CA $cacert -CAkey $cakey -CAcreateserial -out $srvcert
# create client certs
for name in $users; do
cat<<EOF > $dir/$name.cnf
[req]
prompt = no
distinguished_name = dn
[dn]
CN = $name
emailAddress = $name@foo.bar
O = Clixon
L = Stockholm
C = SE
EOF
# Create client key
openssl genrsa -out "$certdir/$name.key" 2048
# Generate CSR (signing request)
openssl req -new -config $dir/$name.cnf -key $certdir/$name.key -out $certdir/$name.csr
# Sign by CA
openssl x509 -req -extfile $dir/$name.cnf -days 1 -passin "pass:password" -in $certdir/$name.csr -CA $cacert -CAkey $cakey -CAcreateserial -out $certdir/$name.crt
done
fi # genkeys
if [ $BE -ne 0 ]; then
new "kill old backend"
sudo clixon_backend -zf $cfg
if [ $? -ne 0 ]; then
err
fi
sudo pkill -f clixon_backend # to be sure
new "start backend -s init -f $cfg"
start_backend -s init -f $cfg
fi
new "wait for backend"
wait_backend
if [ $RC -ne 0 ]; then
new "kill old restconf daemon"
stop_restconf_pre
new "start restconf daemon -c means client certs, -- -s means ssl client cert authentication in example"
start_restconf -f $cfg -c -- -s
fi
#new "wait for restconf"
#wait_restconf XXX
new "auth set authentication config"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><edit-config><target><candidate/></target><config>$RULES</config></edit-config></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "commit it"
expecteof "$clixon_netconf -qf $cfg" 0 "<rpc><commit/></rpc>]]>]]>" "^<rpc-reply><ok/></rpc-reply>]]>]]>$"
new "enable nacm"
expectpart "$(curl -sSik --key $certdir/andy.key --cert $certdir/andy.crt -X PUT -H "Content-Type: application/yang-data+json" -d '{"ietf-netconf-acm:enable-nacm": true}' $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/1.1 204 No Content"
new "admin get x"
expectpart "$(curl -sSik --key $certdir/andy.key --cert $certdir/andy.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/1.1 200 OK" '{"example:x":0}'
new "guest get x"
expectpart "$(curl -sSik --key $certdir/guest.key --cert $certdir/guest.crt -X GET $RCPROTO://localhost/restconf/data/example:x)" 0 "HTTP/1.1 403 Forbidden" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"access denied"}}}'
if [ $RC -ne 0 ]; then
new "Kill restconf daemon"
stop_restconf
fi
if [ $BE -eq 0 ]; then
exit # BE
fi
new "Kill backend"
# Check if premature kill
pid=$(pgrep -u root -f clixon_backend)
if [ -z "$pid" ]; then
err "backend already dead"
fi
# kill backend
stop_backend -f $cfg
rm -rf $dir

View file

@ -23,6 +23,11 @@
# Magic line must be first in script (see README.md)
s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi
# Only works with fcgi and http
if [ "${WITH_RESTCONF}" != "fcgi" -o "$RCPROTO" = https ]; then
if [ "$s" = $0 ]; then exit 0; else return 0; fi # skip
fi
APPNAME=example
: ${clixon_util_stream:=clixon_util_stream}
NCWAIT=10 # Wait (netconf valgrind may need more time)

View file

@ -158,7 +158,7 @@ if [ $BE -ne 0 ]; then
start_backend -s init -f $cfg
fi
new "waiting"
new "wait backend"
wait_backend
if [ $RC -ne 0 ]; then
@ -168,7 +168,7 @@ if [ $RC -ne 0 ]; then
new "start restconf daemon"
start_restconf -f $cfg
new "waiting"
new "wait restconf"
wait_restconf
fi

View file

@ -45,7 +45,10 @@ module clixon-config {
revision 2020-06-17 {
description
"Added: CLICON_CLI_LINES_DEFAULT
Added enum HIDE to CLICON_CLI_GENMODEL";
Added enum HIDE to CLICON_CLI_GENMODEL
Added CLICON_SSL_SERVER_CERT
Added CLICON_SSL_SERVER_KEY
Added CLICON_SSL_CA_CERT";
}
revision 2020-04-23 {
description
@ -377,6 +380,27 @@ module clixon-config {
Setting this value to false makes restconf return not pretty-printed
which may be desirable for performance or tests";
}
leaf CLICON_SSL_SERVER_CERT {
type string;
default "/etc/ssl/certs/clixon-server-crt.pem";
description
"SSL server cert for restconf https. This is not required if you use
--with-restconf=fcgi, ie a reverse-proxy based such as nginx over fcgi";
}
leaf CLICON_SSL_SERVER_KEY {
type string;
default "/etc/ssl/private/clixon-server-key.pem";
description
"SSL server private key for restconf https. This is not required if you use
--with-restconf=fcgi, ie a reverse-proxy based such as nginx over fcgi";
}
leaf CLICON_SSL_CA_CERT {
type string;
default "/etc/ssl/certs/clixon-ca_crt.pem";
description
"SSL CA cert for client authentication. This is not required if you use
--with-restconf=fcgi, ie a reverse-proxy based such as nginx over fcgi";
}
leaf CLICON_CLI_DIR {
type string;
description