#!/usr/bin/env bash # Authentication and authorization and IETF NACM # NACM data node rules # See RFC 8341 A.4. Quote: # # Data node rules are used to control access to specific (config and # non-config) data nodes within the NETCONF content provided by the # server. # This example shows four data node rules: # # deny-nacm: This rule denies the "guest" group any access to the # /nacm subtree. # # permit-acme-config: This rule gives the "limited" group read-write # access to the acme . # # permit-dummy-interface: This rule gives the "limited" and "guest" # groups read-update access to the acme entry named # "dummy". This entry cannot be created or deleted by these groups; # it can only be altered. # # permit-interface: This rule gives the "admin" group read-write # access to all acme entries. # # Test as follows: # 1. limit can read /nacm # 2. guest cannot read /nacm # 3. limited can read and set /config-parameters # 4. guest cannot set /config-parametesr # 5. limit can read and update but not create or delete dummy interface # 6. admin can POST dummy interface # 7. guest|limit can read and PUT dummy interface # 8. guest|limit cannot DELETE dummy interface # 9. admin can DELETE dummy interface # 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 fyang2=$dir/itf.yang # Define default restconfig config: RESTCONFIG RESTCONFIG=$(restconf_config user false) cat < $cfg $cfg ${YANG_INSTALLDIR} $dir $fyang /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 /usr/local/var/$APPNAME internal true $RESTCONFIG EOF # There are two implicit modules defined by RFC 8341 # This is a try to define them cat < $fyang module nacm-example{ yang-version 1.1; namespace "http://example.com/ns/netconf"; prefix ex; import ietf-netconf-acm { prefix nacm; } import itf { prefix acme; } container acme-netconf{ container config-parameters{ list parameter{ key name; leaf name{ type string; } leaf value{ type string; } } } } } EOF cat < $fyang2 module itf{ yang-version 1.1; namespace "http://example.com/ns/itf"; prefix acme; container interfaces{ list interface{ key name; leaf name{ type string; } leaf value{ type string; } } } } EOF # The groups are slightly modified from RFC8341 A.1 ($USER added in admin group) # The rule-list is from A.4 # Note read-default is set to permit to ensure that deny-nacm is meaningful RULES=$(cat < false permit deny permit $NGROUPS guest-acl guest deny-nacm /nacm:nacm * deny Deny the 'guest' group any access to the /nacm data. limited-acl limited permit-acme-config /ex:acme-netconf/acme:config-parameters read create update delete permit Allow the 'limited' group complete access to the acme NETCONF configuration parameters. Showing long form of 'access-operations' instead of shorthand. guest-limited-acl guest limited permit-dummy-interface /acme:interfaces/acme:interface[acme:name='dummy'] read update permit Allow the 'limited' and 'guest' groups read and update access to the dummy interface. admin-acl admin permit-interface /acme:interfaces/acme:interface * permit Allow the 'admin' group full access to all acme interfaces. EOF ) CONFIG=$(cat < a 72 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 init -f $cfg" start_backend -s init -f $cfg fi new "waiting" wait_backend if [ $RC -ne 0 ]; then new "kill old restconf daemon" stop_restconf_pre new "start restconf daemon" start_restconf -f $cfg fi new "wait restconf" wait_restconf new "auth set authentication config" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$RULES]]>]]>" "^]]>]]>$" new "set app config" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO$CONFIG]]>]]>" "^]]>]]>$" new "commit it" expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO]]>]]>" "^]]>]]>$" new "enable nacm" expectpart "$(curl -u andy:bar $CURLOPTS -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/$HVER 204" #--------------- nacm enabled #user:admin,wilma,guest new "admin can read /nacm" expectpart "$(curl -u andy:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 200" '{"ietf-netconf-acm:enable-nacm":true}' new "1. limit can read /nacm" expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 200" '{"ietf-netconf-acm:enable-nacm":true}' # Comment: it should NOT be access-denied, it should be not found, since then you say it exists but you # dont have access to it which is insecure new "2. guest cannot read /nacm" expectpart "$(curl -u guest:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/ietf-netconf-acm:nacm/enable-nacm)" 0 "HTTP/$HVER 404" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"invalid-value","error-severity":"error","error-message":"Instance does not exist"}}}' # 3. limited can read and set /config-parameters new "3. limited can read config-parameters" expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/nacm-example:acme-netconf/config-parameters)" 0 "HTTP/$HVER 200" '{"nacm-example:config-parameters":{"parameter":\[{"name":"a","value":"72"}\]}}' new "3. limited can set config-parameters" expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/nacm-example:acme-netconf/config-parameters/parameter=a -d '{"nacm-example:parameter":[{"name":"a","value":"93"}]}')" 0 "HTTP/$HVER 204" new "4. guest cannot set /config-parameter" expectpart "$(curl -u guest:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/nacm-example:acme-netconf/config-parameters/parameter=a -d '{"nacm-example:parameter":[{"name":"a","value":"93"}]}')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' # 5. limit can read and update but not create or delete dummy interface new "5a. limit cannot create dummy interface" expectpart "$(curl -u wilma:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/itf:interfaces -d '{"itf:interface":[{"name":"dummy","value":"93"}]}')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "5b. admin can create dummy interface" expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/itf:interfaces -d '{"itf:interface":[{"name":"dummy","value":"93"}]}')" 0 "HTTP/$HVER 201" new "5b. admin can create other interface x as reference" expectpart "$(curl -u andy:bar $CURLOPTS -X POST -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/itf:interfaces -d '{"itf:interface":[{"name":"x","value":"200"}]}')" 0 "HTTP/$HVER 201" new "5c. limit can read dummy interface" expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/itf:interfaces/interface=dummy)" 0 "HTTP/$HVER 200" '{"itf:interface":\[{"name":"dummy","value":"93"}\]}' new "5c. limit can read other interface" expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/itf:interfaces/interface=x)" 0 "HTTP/$HVER 200" '{"itf:interface":\[{"name":"x","value":"200"}\]}' new "5d. limit can update dummy interface" expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/itf:interfaces/interface=dummy -d '{"itf:interface":[{"name":"dummy","value":"42"}]}')" 0 "HTTP/$HVER 204" new "5d. admin can update dummy interface" expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/itf:interfaces/interface=dummy -d '{"itf:interface":[{"name":"dummy","value":"17"}]}')" 0 "HTTP/$HVER 204" new "5d. limit can not update other interface" expectpart "$(curl -u wilma:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/itf:interfaces/interface=x -d '{"itf:interface":[{"name":"x","value":"42"}]}')" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "5d. admin can update other interface" expectpart "$(curl -u andy:bar $CURLOPTS -X PUT -H "Content-Type: application/yang-data+json" $RCPROTO://localhost/restconf/data/itf:interfaces/interface=x -d '{"itf:interface":[{"name":"x","value":"42"}]}')" 0 "HTTP/$HVER 204" new "5d. limit can read dummy interface (again)" expectpart "$(curl -u wilma:bar $CURLOPTS -X GET $RCPROTO://localhost/restconf/data/itf:interfaces/interface=dummy)" 0 "HTTP/$HVER 200" '{"itf:interface":\[{"name":"dummy","value":"17"}\]}' new "5e. limit can not delete dummy interface" expectpart "$(curl -u wilma:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/itf:interfaces/interface=dummy)" 0 "HTTP/$HVER 403" '{"ietf-restconf:errors":{"error":{"error-type":"application","error-tag":"access-denied","error-severity":"error","error-message":"default deny"}}}' new "5e. admin can delete dummy interface" expectpart "$(curl -u andy:bar $CURLOPTS -X DELETE $RCPROTO://localhost/restconf/data/itf:interfaces/interface=dummy)" 0 "HTTP/$HVER 204" if [ $RC -ne 0 ]; then new "Kill restconf daemon" stop_restconf fi if [ $BE -ne 0 ]; then 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 # Set by restconf_config unset RESTCONFIG rm -rf $dir new "endtest" endtest