diff --git a/CHANGELOG.md b/CHANGELOG.md index 824a25ec..9abae496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,7 +31,7 @@ Expected: September 2020 Users may have to change how they access the system * New clixon-config@2020-08-17.yang revision - * Added `CLICON_RESTCONF_ADDRESS` for setting evhtp bind address + * Added options for Restconf evhtp setting default bind socket address and ports `CLICON_RESTCONF_IPV4_ADDR`, `CLICON_RESTCONF_IPV6_ADDR`, `CLICON_RESTCONF_HTTP_PORT`, `CLICON_RESTCONF_HTTPS_PORT` ### Corrected Bugs diff --git a/apps/restconf/restconf_main_evhtp.c b/apps/restconf/restconf_main_evhtp.c index 61917ca2..25ece580 100644 --- a/apps/restconf/restconf_main_evhtp.c +++ b/apps/restconf/restconf_main_evhtp.c @@ -81,9 +81,14 @@ /* Command line options to be passed to getopt(3) */ #define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:o:P:sc" +/* See see listen(5) */ +#define SOCKET_LISTEN_BACKLOG 16 + /* Need global variable to for signal handler XXX */ static clicon_handle _CLICON_HANDLE = NULL; + + /*! Signall terminates process */ static void @@ -594,7 +599,7 @@ main(int argc, cvec *nsctx_global = NULL; /* Global namespace context */ size_t cligen_buflen; size_t cligen_bufthreshold; - uint16_t defaultport = 80; + uint16_t defaultport; uint16_t port = 0; evhtp_t *htp = NULL; struct event_base *evbase = NULL; @@ -602,7 +607,9 @@ main(int argc, int dbg = 0; int use_ssl = 0; int ssl_verify_clients = 0; - char *restconf_address = NULL; + char *restconf_ipv4_addr = NULL; + char *restconf_ipv6_addr = NULL; + int i; /* In the startup, logs to stderr & debug flag set later */ clicon_log_init(__PROGRAM__, LOG_INFO, logdst); @@ -656,11 +663,17 @@ main(int argc, clicon_err(OE_DAEMON, errno, "Setting signal"); goto done; } - + /* Find and read configfile */ if (clicon_options_main(h) < 0) goto done; // stream_path = clicon_option_str(h, "CLICON_STREAM_PATH"); + /* Start with http default port, but change this later if -s is set to https default port */ + if ((i = clicon_option_int(h, "CLICON_RESTCONF_HTTP_PORT")) < 0){ + clicon_err(OE_CFG, EINVAL, "CLICON_RESTCONF_HTTP_PORT not found"); + goto done; + } + defaultport = (uint16_t)i; /* Now rest of options, some overwrite option file */ optind = 1; @@ -703,7 +716,12 @@ main(int argc, } case 's': /* ssl: use https */ use_ssl = 1; - defaultport = 443; /* unless explicit -P ? */ + /* Set to port - note can be overrifden by -P */ + if ((i = clicon_option_int(h, "CLICON_RESTCONF_HTTPS_PORT")) < 0){ + clicon_err(OE_CFG, EINVAL, "CLICON_RESTCONF_HTTPS_PORT not found"); + goto done; + } + defaultport = (uint16_t)i; break; case 'c': /* ssl: verify clients */ ssl_verify_clients = 1; @@ -722,6 +740,10 @@ main(int argc, /* port = defaultport unless explicitly set -P */ if (port == 0) port = defaultport; + if (port == 0){ + clicon_err(OE_DAEMON, EINVAL, "Restconf bind port is 0"); + goto done; + } /* Check server ssl certs */ if (use_ssl){ /* Init evhtp ssl config struct */ @@ -757,6 +779,7 @@ main(int argc, clicon_err(OE_UNIX, errno, "evhtp_new"); goto done; } + /* Here the daemon either uses SSL or not, ie you cant seem to mix http and https :-( */ if (use_ssl){ if (evhtp_ssl_init(htp, ssl_config) < 0){ clicon_err(OE_UNIX, errno, "evhtp_new"); @@ -787,19 +810,49 @@ main(int argc, evhtp_set_gencb(htp, cx_gencb, h); /* bind to a socket, optionally with specific protocol support formatting - * If port is proteced must be done as root? */ - if ((restconf_address = clicon_option_str(h, "CLICON_RESTCONF_ADDRESS")) == NULL){ - clicon_err(OE_CFG, EINVAL, "Missing clixon option: CLICON_RESTCONF_ADDRESS"); + restconf_ipv4_addr = clicon_option_str(h, "CLICON_RESTCONF_IPV4_ADDR"); + restconf_ipv6_addr = clicon_option_str(h, "CLICON_RESTCONF_IPV6_ADDR"); + if ((restconf_ipv4_addr == NULL || strlen(restconf_ipv4_addr)==0) && + (restconf_ipv6_addr == NULL || strlen(restconf_ipv6_addr)==0)){ + clicon_err(OE_DAEMON, EINVAL, "There are no restconf IPv4 or IPv6 bind addresses"); goto done; } - if (evhtp_bind_socket(htp, /* evhtp handle */ - restconf_address, /* string address, eg ipv4: */ - port, /* port */ - 16 /* backlog flag, see listen(5) */ - ) < 0){ - clicon_err(OE_UNIX, errno, "evhtp_bind_socket"); - goto done; + if (restconf_ipv4_addr != NULL && strlen(restconf_ipv4_addr)){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "ipv4:%s", restconf_ipv4_addr); + if (evhtp_bind_socket(htp, /* evhtp handle */ + cbuf_get(cb), /* string address, eg ipv4: */ + port, /* port */ + SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */ + ) < 0){ + clicon_err(OE_UNIX, errno, "evhtp_bind_socket"); + goto done; + } + if (cb) + cbuf_free(cb); + } + if (restconf_ipv6_addr != NULL && strlen(restconf_ipv6_addr)){ + cbuf *cb; + if ((cb = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + cprintf(cb, "ipv6:%s", restconf_ipv6_addr); + if (evhtp_bind_socket(htp, /* evhtp handle */ + cbuf_get(cb), /* string address, eg ipv6: */ + port, /* port */ + SOCKET_LISTEN_BACKLOG /* backlog flag, see listen(5) */ + ) < 0){ + clicon_err(OE_UNIX, errno, "evhtp_bind_socket"); + goto done; + } + if (cb) + cbuf_free(cb); } /* Drop privileges to WWWUSER if started as root */ if (restconf_drop_privileges(h, WWWUSER) < 0) diff --git a/example/main/example.xml b/example/main/example.xml index 85c751bf..92748cf7 100644 --- a/example/main/example.xml +++ b/example/main/example.xml @@ -17,4 +17,7 @@ 0 init disabled + false + 127.0.0.1 + ::1 diff --git a/test/test_nacm_datanode_paths.sh b/test/test_nacm_datanode_paths.sh new file mode 100755 index 00000000..2f9241aa --- /dev/null +++ b/test/test_nacm_datanode_paths.sh @@ -0,0 +1,167 @@ +#!/usr/bin/env bash +# NACM data node rules +# Other NACM datanode tests assume a fixed NACM PATH ruleset +# These tests add and changes datanode rules in paths +# There is problems with namespace for paths +# See test_nacm_datanode_read.sh for original RULES for limit which are here added dynamically +# + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +# Common NACM scripts +. ./nacm.sh + +cfg=$dir/conf_yang.xml +fyang=$dir/nacm-example.yang + +cat < $cfg + + $cfg + /usr/local/share/clixon + $IETFRFC + $dir + $fyang + ietf-netconf:startup + /usr/local/lib/$APPNAME/clispec + /usr/local/lib/$APPNAME/restconf + /usr/local/lib/$APPNAME/cli + $APPNAME + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/lib/$APPNAME/backend + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + false + internal + none + +EOF + +cat < $fyang +module nacm-example{ + yang-version 1.1; + namespace "urn:example:nacm"; + prefix ex; + import ietf-netconf-acm { + prefix nacm; + } + container table{ + container parameters{ + list parameter{ + key name; + leaf name{ + type string; + } + leaf value{ + type string; + } + } + } + } +} +EOF + +# Set initial NACM rules in startup enabling admin and a single param config +cat < $dir/startup_db + + + + + a + 72 + + +
+ + + true + deny + deny + permit + $NGROUPS + $NADMIN + +
+EOF + +new "test params: -f $cfg" + +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -zf $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s startup -f $cfg" + start_backend -s startup -f $cfg +fi + +new "waiting" +wait_backend + +if [ $RC -ne 0 ]; then + new "kill old restconf daemon" + sudo pkill -u $wwwuser -f clixon_restconf + + new "start restconf daemon (-a is enable basic authentication)" + start_restconf -f $cfg -- -a + + new "waiting" + wait_restconf +fi + + + +new "admin read OK" +expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 'HTTP/1.1 200 OK' '{"nacm-example:parameter":\[{"name":"a","value":"72"}\]}' + +new "Fail limit read" +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' + +# Add NACM read rule +new "Add NACM read path rule XML" +expectpart "$(curl -u andy:bar -siS -X POST http://localhost/restconf/data/ietf-netconf-acm:nacm -H 'Content-Type: application/yang-data+xml' -d 'limited-acllimitedtable*read/ex:tablepermit')" 0 "HTTP/1.1 201 Created" + +new "Read NACM rule" +expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/1.1 200 OK" '{"ietf-netconf-acm:rule-list":\[{"name":"limited-acl","group":"limited","rule":\[{"name":"table","module-name":"\*","path":"/ex:table","access-operations":"read","action":"permit"}\]}\]}' + +new "limit read OK" +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 'HTTP/1.1 200 OK' '{"nacm-example:parameter":\[{"name":"a","value":"72"}\]}' + +new "Delete NACM read rule" +expectpart "$(curl -u andy:bar -siS -X DELETE http://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/1.1 204 No Content" + +new "Fail limit read" +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' + +new "Add NACM read path rule JSON" +expectpart "$(curl -u andy:bar -siS -X POST http://localhost/restconf/data/ietf-netconf-acm:nacm -H 'Content-Type: application/yang-data+json' -d '{"ietf-netconf-acm:rule-list":[{"name":"limited-acl","group":"limited","rule":[{"name":"table","module-name":"*","path":"/ex:table","access-operations":"read","action":"permit"}]}]}')" 0 "HTTP/1.1 201 Created" + +new "Read NACM rule" +expectpart "$(curl -u andy:bar -siS -X GET http://localhost/restconf/data/ietf-netconf-acm:nacm/rule-list=limited-acl)" 0 "HTTP/1.1 200 OK" '{"ietf-netconf-acm:rule-list":\[{"name":"limited-acl","group":"limited","rule":\[{"name":"table","module-name":"\*","path":"/ex:table","access-operations":"read","action":"permit"}\]}\]}' + +if false; then +new "Fail limit read" +# XXX: No namespace found for prefix: ex +# See [Cannot create or modify NACM data node access rule with path using JSON encoding #129](https://github.com/clicon/clixon/issues/129) +expectpart "$(curl -u wilma:bar -siS -X GET http://localhost/restconf/data/nacm-example:table/parameters/parameter=a)" 0 'HTTP/1.1 404 Not Found' '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' +fi + +if [ $RC -ne 0 ]; then + new "Kill restconf daemon" + stop_restconf +fi +if [ $BE -ne 0 ]; then # Bring your own backend + 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 +fi + +rm -rf $dir diff --git a/test/test_ssl_certs.sh b/test/test_ssl_certs.sh index afd046e1..92e9bdf3 100755 --- a/test/test_ssl_certs.sh +++ b/test/test_ssl_certs.sh @@ -22,12 +22,13 @@ APPNAME=example fyang=$dir/example.yang cfg=$dir/conf.xml -certdir=$dir/certs +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) @@ -53,7 +54,8 @@ cat < $cfg example_backend.so$ /usr/local/lib/$APPNAME/restconf false - 127.0.0.1 + 127.0.0.1 + ::1 /usr/local/lib/$APPNAME/cli $APPNAME /usr/local/var/$APPNAME/$APPNAME.sock diff --git a/test/vagrant/vagrant.sh b/test/vagrant/vagrant.sh index 4712beaa..66f0d4a6 100755 --- a/test/vagrant/vagrant.sh +++ b/test/vagrant/vagrant.sh @@ -10,7 +10,7 @@ set -eux # if [ $# -ne 1 -a $# -ne 2 ]; then echo "usage: $0 [destroy]\n as defined in https://vagrantcloud.com/search" - exit -1 + exit 255 fi box=$1 # As defined in https://vagrantcloud.com/search diff --git a/yang/clixon/clixon-config@2020-08-17.yang b/yang/clixon/clixon-config@2020-08-17.yang index 1789ce4b..18a1b935 100644 --- a/yang/clixon/clixon-config@2020-08-17.yang +++ b/yang/clixon/clixon-config@2020-08-17.yang @@ -44,7 +44,8 @@ module clixon-config { */ revision 2020-08-17 { description - "Added: CLICON_RESTCONF_ADDRESS"; + "Added: CLICON_RESTCONF_IPV4_ADDR, CLICON_RESTCONF_IPV6_ADDR, + CLICON_RESTCONF_HTTP_PORT, CLICON_RESTCONF_HTTPS_PORT"; } revision 2020-06-17 { description @@ -388,39 +389,57 @@ module clixon-config { Setting this value to false makes restconf return not pretty-printed which may be desirable for performance or tests"; } - leaf CLICON_RESTCONF_ADDRESS { + leaf CLICON_RESTCONF_IPV4_ADDR { type string; - default "ipv4:0.0.0.0"; + default "0.0.0.0"; description - "RESTCONF outward address. - Applies to native http (eg evhtp), not proxy solutions (eg fcgi). - This is essentially from libevhtp: Bind to a socket, optionally with specific protocol - support formatting. The addr can be defined as one of the following: - ipv6: for binding to an IPv6 address. - unix: for binding to a unix named socket - ipv4: for binding to an ipv4 address - If not given, the addr is assumed to be ipv4."; + "RESTCONF IPv4 socket binding address. + Applies to native http by config option --with-restconf=evhtp."; + } + leaf CLICON_RESTCONF_IPV6_ADDR { + type string; + default "::"; + description + "RESTCONF IPv6 socket binding address. + Applies to native http by config option --with-restconf=evhtp."; + } + leaf CLICON_RESTCONF_HTTP_PORT { + type uint16; + default 80; + description + "RESTCONF socket binding port, non-ssl + In the restconf daemon, it can be overriden by -P + Applies to native http only by config option --with-restconf=evhtp."; + } + leaf CLICON_RESTCONF_HTTPS_PORT { + type uint16; + default 443; + description + "RESTCONF socket binding port, ssl + In the restconf daemon, this is the port chosen if -s is given. + Note it can be overriden by -P + Applies to native http by config option --with-restconf=evhtp."; } 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"; + "SSL server cert for restconf https. + Applies to native http only by config option --with-restconf=evhtp."; } 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"; + "SSL server private key for restconf https. + Applies to native http only by config option --with-restconf=evhtp."; } 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"; + "SSL CA cert for client authentication. + Applies to native http only by config option --with-restconf=evhtp."; } leaf CLICON_CLI_DIR { type string;