AAA Infrastructure

Manage user authentication, authorization, and audit using NSO's AAA mechanism.

The Problem

Users log into NSO through the CLI, NETCONF, RESTCONF, SNMP, or via the Web UI. In either case, users need to be authenticated. That is, a user needs to present credentials, such as a password or a public key to gain access. As an alternative, for RESTCONF, users can be authenticated via token validation.

Once a user is authenticated, all operations performed by that user need to be authorized. That is, certain users may be allowed to perform certain tasks, whereas others are not. This is called authorization. We differentiate between the authorization of commands and the authorization of data access.

Structure - Data Models

The NSO daemon manages device configuration including AAA information. NSO manages AAA information as well as uses it. The AAA information describes which users may log in, what passwords they have, and what they are allowed to do. This is solved in NSO by requiring a data model to be both loaded and populated with data. NSO uses the YANG module tailf-aaa.yang for authentication, while ietf-netconf-acm.yang (NETCONF Access Control Model (NACM), RFC 8341) as augmented by tailf-acm.yang is used for group assignment and authorization.

Data Model Contents

The NACM data model is targeted specifically towards access control for NETCONF operations and thus lacks some functionality that is needed in NSO, in particular, support for the authorization of CLI commands and the possibility to specify the context (NETCONF, CLI, etc.) that a given authorization rule should apply to. This functionality is modeled by augmentation of the NACM model, as defined in the tailf-acm.yang YANG module.

The ietf-netconf-acm.yang and tailf-acm.yang modules can be found in $NCS_DIR/src/ncs/yang directory in the release, while tailf-aaa.yang can be found in the $NCS_DIR/src/ncs/aaa directory.

NACM options related to services are modeled by augmentation of the NACM model, as defined in the tailf-ncs-acm.yang YANG module. The tailf-ncs-acm.yang can be found in $NCS_DIR/src/ncs/yang directory in the release.

The complete AAA data model defines a set of users, a set of groups, and a set of rules. The data model must be populated with data that is subsequently used by by NSO itself when it authenticates users and authorizes user data access. These YANG modules work exactly like all other fxs files loaded into the system with the exception that NSO itself uses them. The data belongs to the application, but NSO itself is the user of the data.

Since NSO requires a data model for the AAA information for its operation, it will report an error and fail to start if these data models cannot be found.

AAA-related Items in ncs.conf

NSO itself is configured through a configuration file - ncs.conf. In that file, we have the following items related to authentication and authorization:

  • /ncs-config/aaa/ssh-server-key-dir: If SSH termination is enabled for NETCONF or the CLI, the NSO built-in SSH server needs to have server keys. These keys are generated by the NSO install script and by default end up in $NCS_DIR/etc/ncs/ssh. It is also possible to use OpenSSH to terminate NETCONF or the CLI. If OpenSSH is used to terminate SSH traffic, this setting has no effect.

  • /ncs-config/aaa/ssh-pubkey-authentication: If SSH termination is enabled for NETCONF or the CLI, this item controls how the NSO SSH daemon locates the user keys for public key authentication. See Public Key Login for details.

  • /ncs-config/aaa/local-authentication/enabled: The term 'local user' refers to a user stored under /aaa/authentication/users. The alternative is a user unknown to NSO, typically authenticated by PAM. By default, NSO first checks local users before trying PAM or external authentication. Local authentication is practical in test environments. It is also useful when we want to have one set of users that are allowed to log in to the host with normal shell access and another set of users that are only allowed to access the system using the normal encrypted, fully authenticated, northbound interfaces of NSO. If we always authenticate users through PAM, it may make sense to set this configurable to false. If we disable local authentication, it implicitly means that we must use either PAM authentication or external authentication. It also means that we can leave the entire data trees under /aaa/authentication/users and, in the case of external authentication, also /nacm/groups (for NACM) or /aaa/authentication/groups (for legacy tailf-aaa) empty.

  • /ncs-config/aaa/pam: NSO can authenticate users using PAM (Pluggable Authentication Modules). PAM is an integral part of most Unix-like systems. PAM is a complicated - albeit powerful - subsystem. It may be easier to have all users stored locally on the host, However, if we want to store users in a central location, PAM can be used to access the remote information. PAM can be configured to perform most login scenarios including RADIUS and LDAP. One major drawback with PAM authentication is that there is no easy way to extract the group information from PAM. PAM authenticates users, it does not also assign a user to a set of groups. PAM authentication is thoroughly described later in this chapter.

  • /ncs-config/aaa/default-group: If this configuration parameter is defined and if the group of a user cannot be determined, a logged-in user ends up in the given default group.

  • /ncs-config/aaa/external-authentication: NSO can authenticate users using an external executable. This is further described later in External Authentication. As an alternative, you may consider using package authentication.

  • /ncs-config/aaa/external-validation: NSO can authenticate users by validation of tokens using an external executable. This is further described later in External Token Validation. Where external authentication uses a username and password to authenticate a user, external validation uses a token. The validation script should use the token to authenticate a user and can, optionally, also return a new token to be returned with the result of the request. It is currently only supported for RESTCONF.

  • /ncs-config/aaa/external-challenge: NSO has support for multi-factor authentication by sending challenges to a user. Challenges may be sent from any of the external authentication mechanisms but are currently only supported by JSON-RPC and CLI over SSH. This is further described later in External Multi-factor Authentication.

  • /ncs-config/aaa/package-authentication: NSO can authenticate users using package authentication. It extends the concept of external authentication by allowing multiple packages to be used for authentication instead of a single executable. This is further described in Package Authentication.

  • /ncs-config/aaa/single-sign-on: With this setting enabled, NSO invokes Package Authentication on all requests to HTTP endpoints with the /sso prefix. This way, Package Authentication packages that require custom endpoints can expose them under the /sso base route. For example, a SAMLv2 Single Sign-On (SSO) package needs to process requests to an AssertionConsumerService endpoint, such as /sso/saml/acs, and therefore requires enabling this setting. This is a valid authentication method for WEB UI and JSON-RPC interfaces and needs Package Authentication to be enabled as well.

  • /ncs-config/aaa/single-sign-on/enable-automatic-redirect: If only one Single Sign-On package is configured (a package with single-sign-on-url set in package-meta-data.xml) and also this setting is enabled, NSO automatically redirects all unauthenticated access attempts to the configured single-sign-on-url.

Authentication

Depending on the northbound management protocol, when a user session is created in NSO, it may or may not be authenticated. If the session is not yet authenticated, NSO's AAA subsystem is used to perform authentication and authorization, as described below. If the session already has been authenticated, NSO's AAA assigns groups to the user as described in Group Membership, and performs authorization, as described in Authorization.

The authentication part of the data model can be found in tailf-aaa.yang:

    container authentication {
      tailf:info "User management";
      container users {
        tailf:info "List of local users";
        list user {
          key name;
          leaf name {
            type string;
            tailf:info "Login name of the user";
          }
          leaf uid {
            type int32;
            mandatory true;
            tailf:info "User Identifier";
          }
          leaf gid {
            type int32;
            mandatory true;
            tailf:info "Group Identifier";
          }
          leaf password {
            type passwdStr;
            mandatory true;
          }
          leaf ssh_keydir {
            type string;
            mandatory true;
            tailf:info "Absolute path to directory where user's ssh keys
                        may be found";
          }
          leaf homedir {
            type string;
            mandatory true;
            tailf:info "Absolute path to user's home directory";
          }
        }
      }
    }

AAA authentication is used in the following cases:

  • When the built-in SSH server is used for NETCONF and CLI sessions.

  • For Web UI sessions and REST access.

  • When the method Maapi.Authenticate() is used.

NSO's AAA authentication is not used in the following cases:

  • When NETCONF uses an external SSH daemon, such as OpenSSH.

    In this case, the NETCONF session is initiated using the program netconf-subsys, as described in NETCONF Transport Protocols in Northbound APIs.

  • When NETCONF uses TCP, as described in NETCONF Transport Protocols in Northbound APIs, e.g. through the command netconf-console.

  • When accessing the CLI by invoking the ncs_cli, e.g. through an external SSH daemon, such as OpenSSH, or a telnet daemon. An important special case here is when a user has shell access to the host and runs ncs_cli from the shell. This command, as well as direct access to the IPC socket, allows for authentication bypass. It is crucial to consider this case for your deployment. If non-trusted users have shell access to the host, IPC access must be restricted. See Authenticating IPC Access.

  • When SNMP is used, SNMP has its own authentication mechanisms. See NSO SNMP Agent in Northbound APIs.

  • When the method Maapi.startUserSession() is used without a preceding call of Maapi.authenticate().

Public Key Login

When a user logs in over NETCONF or the CLI using the built-in SSH server, with a public key login, the procedure is as follows.

The user presents a username in accordance with the SSH protocol. The SSH server consults the settings for /ncs-config/aaa/ssh-pubkey-authentication and /ncs-config/aaa/local-authentication/enabled .

  1. If ssh-pubkey-authentication is set to local, and the SSH keys in /aaa/authentication/users/user{$USER}/ssh_keydir match the keys presented by the user, authentication succeeds.

  2. Otherwise, if ssh-pubkey-authentication is set to system, local-authentication is enabled, and the SSH keys in /aaa/authentication/users/user{$USER}/ssh_keydir match the keys presented by the user, authentication succeeds.

  3. Otherwise, if ssh-pubkey-authentication is set to system and the user /aaa/authentication/users/user{$USER} does not exist, but the user does exist in the OS password database, the keys in the user's $HOME/.ssh directory are checked. If these keys match the keys presented by the user, authentication succeeds.

  4. Otherwise, authentication fails.

In all cases the keys are expected to be stored in a file called authorized_keys (or authorized_keys2 if authorized_keys does not exist), and in the native OpenSSH format (i.e. as generated by the OpenSSH ssh-keygen command). If authentication succeeds, the user's group membership is established as described in Group Membership.

This is exactly the same procedure that is used by the OpenSSH server with the exception that the built-in SSH server also may locate the directory containing the public keys for a specific user by consulting the /aaa/authentication/users tree.

Setting up Public Key Login

We need to provide a directory where SSH keys are kept for a specific user and give the absolute path to this directory for the /aaa/authentication/users/user/ssh_keydir leaf. If a public key login is not desired at all for a user, the value of the ssh_keydir leaf should be set to "", i.e. the empty string. Similarly, if the directory does not contain any SSH keys, public key logins for that user will be disabled.

The built-in SSH daemon supports DSA, RSA, and ED25519 keys. To generate and enable RSA keys of size 4096 bits for, say, user "bob", the following steps are required.

On the client machine, as user "bob", generate a private/public key pair as:

# ssh-keygen -b 4096 -t rsa
Generating public/private rsa key pair.
Enter file in which to save the key (/home/bob/.ssh/id_rsa):
Created directory '/home/bob/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/bob/.ssh/id_rsa.
Your public key has been saved in /home/bob/.ssh/id_rsa.pub.
The key fingerprint is:
ce:1b:63:0a:f9:d4:1d:04:7a:1d:98:0c:99:66:57:65 bob@buzz
# ls -lt ~/.ssh
total 8
-rw-------  1 bob users 3247 Apr  4 12:28 id_rsa
-rw-r--r--  1 bob users  738 Apr  4 12:28 id_rsa.pub

Now we need to copy the public key to the target machine where the NETCONF or CLI SSH client runs.

Assume we have the following user entry:

<user>
  <name>bob</name>
  <uid>100</uid>
  <gid>10</gid>
  <password>$1$feedbabe$nGlMYlZpQ0bzenyFOQI3L1</password>
  <ssh_keydir>/var/system/users/bob/.ssh</ssh_keydir>
  <homedir>/var/system/users/bob</homedir>
</user>

We need to copy the newly generated file id_rsa.pub, which is the public key, to a file on the target machine called /var/system/users/bob/.ssh/authorized_keys.

Since the release of OpenSSH 7.0, support of ssh-dss host and user keys is disabled by default. If you want to continue using these, you may re-enable it using the following options for OpenSSH client:

HostKeyAlgorithms=+ssh-dss
PubkeyAcceptedKeyTypes=+ssh-dss

You can find full instructions at OpenSSH Legacy Options webpage.

Password Login

Password login is triggered in the following cases:

  • When a user logs in over NETCONF or the CLI using the built-in SSH server, with a password. The user presents a username and a password in accordance with the SSH protocol.

  • When a user logs in using the Web UI. The Web UI asks for a username and password.

  • When the method Maapi.authenticate() is used.

In this case, NSO will by default try local authentication, PAM, external authentication, and package authentication in that order, as described below. It is possible to change the order in which these are tried, by modifying the ncs.conf. parameter /ncs-config/aaa/auth-order. See ncs.conf(5) in Manual Pages for details.

  1. If /aaa/authentication/users/user{$USER} exists and the presented password matches the encrypted password in /aaa/authentication/users/user{$USER}/password, the user is authenticated.

  2. If the password does not match or if the user does not exist in /aaa/authentication/users, PAM login is attempted, if enabled. See PAM for details.

  3. If all of the above fails and external authentication is enabled, the configured executable is invoked. See External Authentication for details.

If authentication succeeds, the user's group membership is established as described in Group Membership.

PAM

On operating systems supporting PAM, NSO also supports PAM authentication. Using PAM, authentication with NSO can be very convenient since it allows us to have the same set of users and groups having access to NSO as those that have access to the UNIX/Linux host itself.

PAM is the recommended way to authenticate NSO users.

If we use PAM, we do not have to have any users or any groups configured in the NSO aaa namespace at all.

To configure PAM we typically need to do the following:

  1. Remove all users and groups from the AAA initialization XML file.

  2. Enable PAM in ncs.conf by adding the following to the AAA section in ncs.conf. The service name specifies the PAM service, typically a file in the directory /etc/pam.d, but may alternatively, be an entry in a file /etc/pam.conf depending on OS and version. Thus, it is possible to have a different login procedure for NSO than for the host itself.

    <pam>
      <enabled>true</enabled>
      <service>common-auth</service>
    </pam>
  3. If PAM is enabled and we want to use PAM for login, the system may have to run as root. This depends on how PAM is configured locally. However, the default system authentication will typically require root, since the PAM libraries then read /etc/shadow. If we don't want to run NSO as root, the solution here is to change the owner of a helper program called $NCS_DIR/lib/ncs/lib/core/pam/priv/epam and also set the setuid bit.

    # cd $NCS_DIR/lib/ncs/lib/core/pam/priv/
    # chown root:root epam
    # chmod u+s epam

As an example, say that we have a user test in /etc/passwd, and furthermore:

# grep test /etc/group
operator:x:37:test
admin:x:1001:test

Thus, the test user is part of the admin and the operator groups and logging in to NSO as the test user through CLI SSH, Web UI, or NETCONF, renders the following in the audit log.

<INFO> 28-Jan-2009::16:05:55.663 buzz ncs[14658]: audit user: test/0 logged
    in over ssh from 127.0.0.1 with authmeth:password
<INFO> 28-Jan-2009::16:05:55.670 buzz ncs[14658]: audit user: test/5 assigned
    to groups: operator,admin
<INFO> 28-Jan-2009::16:05:57.655 buzz ncs[14658]: audit user: test/5 CLI 'exit'

Thus, the test user was found and authenticated from /etc/passwd, and the crucial group assignment of the test user was done from /etc/group.

If we wish to be able to also manipulate the users, their passwords, etc on the device, we can write a private YANG model for that data, store that data in CDB, set up a normal CDB subscriber for that data, and finally when our private user data is manipulated, our CDB subscriber picks up the changes and changes the contents of the relevant /etc files.

External Authentication

A common situation is when we wish to have all authentication data stored remotely, not locally, for example on a remote RADIUS or LDAP server. This remote authentication server typically not only stores the users and their passwords but also the group information.

If we wish to have not only the users but also the group information stored on a remote server, the best option for NSO authentication is to use external authentication.

If this feature is configured, NSO will invoke the executable configured in /ncs-config/aaa/external-authentication/executable in ncs.conf , and pass the username and the clear text password on stdin using the string notation: "[user;password;]\n".

For example, if the user bob attempts to log in over SSH using the password 'secret', and external authentication is enabled, NSO will invoke the configured executable and write "[bob;secret;]\n" on the stdin stream for the executable. The task of the executable is then to authenticate the user and also establish the username-to-groups mapping.

For example, the executable could be a RADIUS client which utilizes some proprietary vendor attributes to retrieve the groups of the user from the RADIUS server. If authentication is successful, the program should write accept followed by a space-separated list of groups that the user is a member of, and additional information as described below. Again, assuming that bob's password indeed was 'secret', and that bob is a member of the admin and the lamers groups, the program should write accept admin lamers $uid $gid $supplementary_gids $HOME on its standard output and then exit.

There is a general limit of 16000 bytes of output from the externalauth program.

Thus, the format of the output from an externalauth program when authentication is successful should be:

"accept $groups $uid $gid $supplementary_gids $HOME\n"

Where:

  • $groups is a space-separated list of the group names the user is a member of.

  • $uid is the UNIX integer user ID that NSO should use as a default when executing commands for this user.

  • $gid is the UNIX integer group ID that NSO should use as a default when executing commands for this user.

  • $supplementary_gids is a (possibly empty) space-separated list of additional UNIX group IDs the user is also a member of.

  • $HOME is the directory that should be used as HOME for this user when NSO executes commands on behalf of this user.

It is further possible for the program to return a token on successful authentication, by using "accept_token" instead of "accept":

"accept_token $groups $uid $gid $supplementary_gids $HOME $token\n"

Where:

  • $token is an arbitrary string. NSO will then, for some northbound interfaces, include this token in responses.

It is also possible for the program to return additional information on successful authentication, by using "accept_info" instead of "accept":

"accept_info $groups $uid $gid $supplementary_gids $HOME $info\n"

Where:

  • $info is some arbitrary text. NSO will then just append this text to the generated audit log message (CONFD_EXT_LOGIN).

Yet another possibility is for the program to return a warning that the user's password is about to expire, by using "accept_warning" instead of "accept":

"accept_warning $groups $uid $gid $supplementary_gids $HOME $warning\n"

Where:

  • $warning is an appropriate warning message. The message will be processed by NSO according to the setting of /ncs-config/aaa/expiration-warning in ncs.conf.

There is also support for token variations of "accept_info" and "accept_warning" namely "accept_token_info" and "accept_token_warning". Both "accept_token_info" and "accept_token_warning" expect the external program to output exactly the same as described above with the addition of a token after $HOME:

  • "accept_token_info $groups $uid $gid $supplementary_gids $HOME $token $info\n"

  • "accept_token_warning $groups $uid $gid $supplementary_gids $HOME $token $warning\n"

If authentication failed, the program should write "reject" or "abort", possibly followed by a reason for the rejection, and a trailing newline. For example, "reject Bad password\n" or just "abort\n". The difference between "reject" and "abort" is that with "reject", NSO will try subsequent mechanisms configured for /ncs-config/aaa/auth-order in ncs.conf (if any), while with "abort", the authentication fails immediately. Thus "abort" can prevent subsequent mechanisms from being tried, but when external authentication is the last mechanism (as in the default order), it has the same effect as "reject".

Supported by some northbound APIs, such as JSON-RPC and CLI over SSH, the external authentication may also choose to issue a challenge:

"challenge $challenge-id $challenge-prompt\n"

The challenge-prompt may be multi-line, why it must be base64 encoded.

For more information on multi-factor authentication, see External Multi-Factor Authentication.

When external authentication is used, the group list returned by the external program is prepended by any possible group information stored locally under the /aaa tree. Hence when we use external authentication it is indeed possible to have the entire /aaa/authentication tree empty. The group assignment performed by the external program will still be valid and the relevant groups will be used by NSO when the authorization rules are checked.

External Token Validation

When username and password authentication is not feasible, authentication by token validation is possible. Currently, only RESTCONF supports this mode of authentication. It shares all properties of external authentication, but instead of a username and password, it takes a token as input. The output is also almost the same, the only difference is that it is also expected to output a username.

If this feature is configured, NSO will invoke the executable configured in /ncs-config/aaa/external-validation/executable in ncs.conf , and pass the token on stdin using the string notation: "[token;]\n".

For example if the user bob attempts to log over RESTCONF using the token topsecret, and external validation is enabled, NSO will invoke the configured executable and write "[topsecret;]\n" on the stdin stream for the executable.

The task of the executable is then to validate the token, thereby authenticating the user and also establishing the username and username-to-groups mapping.

For example, the executable could be a FUSION client that utilizes some proprietary vendor attributes to retrieve the username and groups of the user from the FUSION server. If token validation is successful, the program should write accept followed by a space-separated list of groups that the user is a member of, and additional information as described below. Again, assuming that bob's token indeed was topsecret, and that bob is a member of the admin and the lamers groups, the program should write accept admin lamers $uid $gid $supplementary_gids $HOME $USER on its standard output and then exit.

There is a general limit of 16000 bytes of output from the externalvalidation program.

Thus the format of the output from an externalvalidation program when token validation authentication is successful should be:

"accept $groups $uid $gid $supplementary_gids $HOME $USER\n"

Where:

  • $groups is a space-separated list of the group names the user is a member of.

  • $uid is the UNIX integer user ID NSO should use as a default when executing commands for this user.

  • $gid is the UNIX integer group ID NSO should use as a default when executing commands for this user.

  • $supplementary_gids is a (possibly empty) space-separated list of additional UNIX group IDs the user is also a member of.

  • $HOME is the directory that should be used as HOME for this user when NSO executes commands on behalf of this user.

  • $USER is the user derived from mapping the token.

It is further possible for the program to return a new token on successful token validation authentication, by using "accept_token" instead of "accept":

"accept_token $groups $uid $gid $supplementary_gids $HOME $USER $token\n"

Where:

  • $token is an arbitrary string. NSO will then, for some northbound interfaces, include this token in responses.

It is also possible for the program to return additional information on successful token validation authentication, by using "accept_info" instead of "accept":

"accept_info $groups $uid $gid $supplementary_gids $HOME $USER $info\n"

Where:

  • $info is some arbitrary text. NSO will then just append this text to the generated audit log message (CONFD_EXT_LOGIN).

Yet another possibility is for the program to return a warning that the user's password is about to expire, by using "accept_warning" instead of "accept":

"accept_warning $groups $uid $gid $supplementary_gids $HOME $USER $warning\n"

Where:

  • $warning is an appropriate warning message. The message will be processed by NSO according to the setting of /ncs-config/aaa/expiration-warning in ncs.conf.

There is also support for token variations of "accept_info" and "accept_warning" namely "accept_token_info" and "accept_token_warning". Both "accept_token_info" and "accept_token_warning" expect the external program to output exactly the same as described above with the addition of a token after $USER:

"accept_token_info $groups $uid $gid $supplementary_gids $HOME $USER $token $info\n"

"accept_token_warning $groups $uid $gid $supplementary_gids $HOME $USER $token $warning\n"

If token validation authentication fails, the program should write "reject" or "abort", possibly followed by a reason for the rejection and a trailing newline. For example "reject Bad password\n" or just "abort\n". The difference between "reject" and "abort" is that with "reject", NSO will try subsequent mechanisms configured for /ncs-config/aaa/validation-order in ncs.conf (if any), while with "abort", the token validation authentication fails immediately. Thus "abort" can prevent subsequent mechanisms from being tried. Currently, the only available token validation authentication mechanism is the external one.

Supported by some northbound APIs, such as JSON-RPC and CLI over SSH, the external validation may also choose to issue a challenge:

"challenge $challenge-id $challenge-prompt\n"

The challenge prompt may be multi-line, why it must be base64 encoded.

For more information on multi-factor authentication, see External Multi-Factor Authentication.

External Multi-Factor Authentication

When username, password, or token authentication is not enough, a challenge may be sent from any of the external authentication mechanisms to the user. A challenge consists of a challenge ID and a base64 encoded challenge prompt, and a user is supposed to send a response to the challenge. Currently, only JSONRPC and CLI over SSH support multi-factor authentication. Responses to challenges of multi-factor authentication have the same output as the token authentication mechanism.

If this feature is configured, NSO will invoke the executable configured in /ncs-config/aaa/external-challenge/executable in ncs.conf , and pass the challenge ID and response on stdin using the string notation: "[challenge-id;response;]\n".

For example, a user bob has received a challenge from external authentication, external validation, or external challenge and then attempts to log in over JSON-RPC with a response to the challenge using challenge ID "22efa",response:"ae457b". The external challenge mechanism is enabled, NSO will invoke the configured executable and write "[22efa;ae457b;]\n" on the stdin stream for the executable.

The task of the executable is then to validate the challenge ID, and response combination, thereby authenticating the user and also establishing the username and username-to-groups mapping.

For example, the executable could be a RADIUS client which utilizes some proprietary vendor attributes to retrieve the username and groups of the user from the RADIUS server. If challenge ID, response validation is successful, the program should write "accept " followed by a space-separated list of groups the user is a member of, and additional information as described below. Again, assuming that bob's challenge ID, the response combination indeed was "22efa", "ae457b" and that bob is a member of the admin and the lamers groups, the program should write "accept admin lamers $uid $gid $supplementary_gids $HOME $USER\n" on its standard output and then exit.

There is a general limit of 16000 bytes of output from the externalchallenge program.

Thus the format of the output from an externalchallenge program when challenge-based authentication is successful should be:

"accept $groups $uid $gid $supplementary_gids $HOME $USER\n"

Where:

  • $groups is a space-separated list of the group names the user is a member of.

  • $uid is the UNIX integer user ID NSO should use as a default when executing commands for this user.

  • $gid is the UNIX integer group ID NSO should use as a default when executing commands for this user.

  • $supplementary_gids is a (possibly empty) space-separated list of additional UNIX group IDs the user is also a member of.

  • $HOME is the directory that should be used as HOME for this user when NSO executes commands on behalf of this user.

  • $USER is the user derived from mapping the challenge ID, response.

It is further possible for the program to return a token on successful authentication, by using "accept_token" instead of "accept":

"accept_token $groups $uid $gid $supplementary_gids $HOME $USER $token\n"

Where:

  • $token is an arbitrary string. NSO will then, for some northbound interfaces, include this token in responses.

It is also possible for the program to return additional information on successful authentication, by using "accept_info" instead of "accept":

"accept_info $groups $uid $gid $supplementary_gids $HOME $USER $info\n"

Where:

  • $info is some arbitrary text. NSO will then just append this text to the generated audit log message (CONFD_EXT_LOGIN).

Yet another possibility is for the program to return a warning that the user's password is about to expire, by using "accept_warning" instead of "accept":

"accept_warning $groups $uid $gid $supplementary_gids $HOME $USER $warning\n"

Where:

  • $warning is an appropriate warning message. The message will be processed by NSO according to the setting of /ncs-config/aaa/expiration-warning in ncs.conf.

There is also support for token variations of "accept_info" and "accept_warning" namely "accept_token_info" and "accept_token_warning". Both "accept_token_info" and "accept_token_warning" expects the external program to output exactly the same as described above with the addition of a token after $USER:

"accept_token_info $groups $uid $gid $supplementary_gids $HOME $USER $token $info\n"

"accept_token_warning $groups $uid $gid $supplementary_gids $HOME $USER $token $warning\n"

If authentication fails, the program should write "reject" or "abort", possibly followed by a reason for the rejection and a trailing newline. For example "reject Bad challenge response\n" or just "abort\n". The difference between "reject" and "abort" is that with "reject", NSO will try subsequent mechanisms configured for /ncs-config/aaa/challenge-order in ncs.conf (if any), while with "abort", the challenge-response authentication fails immediately. Thus "abort" can prevent subsequent mechanisms from being tried. Currently, the only available challenge-response authentication mechanism is the external one.

Supported by some northbound APIs, such as JSON-RPC and CLI over SSH, the external challenge may also choose to issue a new challenge:

"challenge $challenge-id $challenge-prompt\n"

The challenge prompt may be multi-line, so it must be base64 encoded.

Note that when using challenges with the CLI over SSH, the /ncs-config/cli/ssh/use-keyboard-interactive> need to be set to true for the challenges to be sent correctly to the client.

The configuration of the SSH client used may need to be given the option to allow a higher number of allowed number of password prompts, e.g. -o NumberOfPasswordPrompts, else the default number may introduce an unexpected behavior when the client is presented with multiple challenges.

Package Authentication

The Package Authentication functionality allows for packages to handle the NSO authentication in a customized fashion. Authentication data can e.g. be stored remotely, and a script in the package is used to communicate with the remote system.

Compared to external authentication, the Package Authentication mechanism allows specifying multiple packages to be invoked in the order they appear in the configuration. NSO provides implementations for LDAP, SAMLv2, and TACACS+ protocols with packages available in $NCS_DIR/packages/auth/. Additionally, you can implement your own authentication packages as detailed below.

Authentication packages are NSO packages with the required content of an executable file scripts/authenticate. This executable basically follows the same API, and limitations, as the external auth script, but with a different input format and some additional functionality. Other than these requirements, it is possible to customize the package arbitrarily.

Package authentication is supported for Single Sign-On (see Single Sign-On in Web UI), JSON-RPC, and RESTCONF. Note that Single Sign-On and (non-batch) JSON-RPC allow all functionality while the RESTCONF interface will treat anything other than a "accept_username" reply from the package as if authentication failed!

Package authentication is enabled by setting the ncs.conf options /ncs-config/aaa/package-authentication/enabled to true, and adding the package by name in the /ncs-config/aaa/package-authentication/packages list. The order of the configured packages is the order that the packages will be used when attempting to authenticate a user. See ncs.conf(5) in Manual Pages for details.

If this feature is configured in ncs.conf, NSO will for each configured package invoke script/authenticate, and pass username, password, and original HTTP request (i.e. the user-supplied next query parameter), HTTP request, HTTP headers, HTTP body, client source IP, client source port, northbound API context, and protocol on stdin using the string notation: "[user;password;orig_request;request;headers;body;src-ip;src-port;ctx;proto;]\n".

The fields user, password, orig_request, request, headers, and body are all base64 encoded.

If the body length exceeds the partial_post_size of the RESTCONF server, the body passed to the authenticate script will only contain the string '==nso_package_authentication_partial_body=='.

The original request will be prefixed with the string ==nso_package_authentication_next== before the base64 encoded part. This means supplying the next query parameter value /my-location will pass the following string to the authentication script: ==nso_package_authentication_next==L215LWxvY2F0aW9u.

For example, if an unauthenticated user attempts to start a single sign-on process over northbound HTTP-based APIs with the cisco-nso-saml2-auth package, package authentication is enabled and configured with packages, and also single sign-on is enabled, NSO will, for each configured package, invoke the executable scripts/authenticate and write "[;;;R0VUIC9zc28vc2FtbC9sb2dpbi8gSFRUUC8xLjE=;;;127.0.0.1;59226;webui;https;]\n". on the stdin stream for the executable.

For clarity, the base64 decoded contents sent to stdin presented: "[;;;GET /sso/saml/login/ HTTP/1.1;;;127.0.0.1;54321;webui;https;]\n".

The task of the package is then to authenticate the user and also establish the username-to-groups mapping.

For example, the package could support a SAMLv2 authentication protocol which communicates with an Identity Provider (IdP) for authentication. If authentication is successful, the program should write either "accept", or "accept_username", depending on whether the authentication is started with a username or if an external entity handles the entire authentication and supplies the username for a successful authentication. (SAMLv2 uses accept_username, since the IdP handles the entire authentication.) The "accept_username " is followed by a username and then followed by a space-separated list of groups the user is a member of, and additional information as described below. If authentication is successful and the authenticated user bob is a member of the groups admin and wheel, the program should write "accept_username bob admin wheel 1000 1000 100 /home/bob\n" on its standard output and then exit.

There is a general limit of 16000 bytes of output from the "packageauth" program.

Thus the format of the output from a packageauth program when authentication is successful should be either the same as from externalauth (see External Authentication) or the following:

"accept_username $USER $groups $uid $gid $supplementary_gids $HOME\n"

Where:

  • $USER is the user derived during the execution of the "packageauth" program.

  • $groups is a space-separated list of the group names the user is a member of.

  • $uid is the UNIX integer user ID NSO should use as a default when executing commands for this user.

  • $gid is the UNIX integer group ID NSO should use as a default when executing commands for this user.

  • $supplementary_gids is a (possibly empty) space-separated list of additional UNIX group IDs the user is also a member of.

  • $HOME is the directory that should be used as HOME for this user when NSO executes commands on behalf of this user.

In addition to the externalauth API, the authentication packages can also return the following responses:

  • unknown 'reason' - (reason being plain-text) if they can't handle authentication for the supplied input.

  • redirect 'url' - (url being base64 encoded) for an HTTP redirect.

  • content 'content-type' 'content' - (content-type being plain-text mime-type and content being base64 encoded) to relay supplied content.

  • accept_username_redirect url $USER $groups $uid $gid $supplementary_gids $HOME - which combines the accept_username and redirect.

It is also possible for the program to return additional information on successful authentication, by using "accept_info" instead of "accept":

"accept_info $groups $uid $gid $supplementary_gids $HOME $info\n"

Where:

  • $info is some arbitrary text. NSO will then just append this text to the generated audit log message (NCS_PACKAGE_AUTH_SUCCESS).

Yet another possibility is for the program to return a warning that the user's password is about to expire, by using "accept_warning" instead of "accept":

"accept_warning $groups $uid $gid $supplementary_gids $HOME $warning\n"

Where:

  • $warning is an appropriate warning message. The message will be processed by NSO according to the setting of /ncs-config/aaa/expiration-warning in ncs.conf.

If authentication fails, the program should write "reject" or "abort", possibly followed by a reason for the rejection and a trailing newline. For example "reject 'Bad password'\n" or just "abort\n". The difference between "reject" and "abort" is that with "reject", NSO will try subsequent mechanisms configured for /ncs-config/aaa/auth-order, and packages configured for /ncs-config/aaa/package-authentication/packages in ncs.conf (if any), while with "abort", the authentication fails immediately. Thus "abort" can prevent subsequent mechanisms from being tried, but when external authentication is the last mechanism (as in the default order), it has the same effect as "reject".

When package authentication is used, the group list returned by the package executable is prepended by any possible group information stored locally under the /aaa tree. Hence when package authentication is used, it is indeed possible to have the entire /aaa/authentication tree empty. The group assignment performed by the external program will still be valid and the relevant groups will be used by NSO when the authorization rules are checked.

Username/Password Package Authentication for CLI

Package authentication will invoke the scripts/authenticate when a user tries to authenticate using CLI. In this case, only the username, password, client source IP, client source port, northbound API context, and protocol will be passed to the script.

When serving a username/password request, script output other than accept, challenge or abort will be treated as if authentication failed.

Package Challenges

When this is enabled, /ncs-config/aaa/package-authentication/package-challenge/enabled is set to true, packages will also be used to try to resolve challenges sent to the server and are only supported by CLI over SSH. The script script/challenge will be invoked passing challenge ID, response, client source IP, client source port, northbound API context, and protocol on stdin using the string notation: "[challengeid;response;src-ip;src-port;ctx;proto;]\n" . The output should follow that of the authenticate script.

The fields challengeid and response are base64 encoded when passed to the script.

Authenticating IPC Access

NSO communicates with clients (Python and Java client libraries, ncs_cli, netconf-subsys, and others) using the NSO IPC socket. The protocol used allows the client to provide user and group information to use for authorization in NSO, effectively delegating authentication to the client.

By default, only local connections to the IPC socket are allowed. If all local clients are considered trusted, the socket can provide unauthenticated access, with the client-supplied user name. This is what the --user option of ncs_cli does. For example:

ncs_cli --user admin

connects to NSO as the user admin. The same is possible for the group. This unauthenticated access is currently the default.

The main condition here is that all clients connecting to the socket are trusted to use the correct user and group information. That is often not the case, such as untrusted users having shell access to the host to run ncs_cli or otherwise initiate local connections to the IPC socket. Then access to the socket must be restricted.

In general, authenticating access to the IPC socket is a security best practice and should always be used. When NSO is configured to use Unix domain sockets for IPC, it authenticates the client based on the UID of the other end of the socket connection. Alternatively, the system can be instructed to use TCP sockets. In this case, the system should be configured to use an access check, where every IPC client must prove that it has access to a pre-shared key. See Restricting Access to the IPC Socket on how to enable it.

UID-based Authentication for Unix Sockets

NSO will use Unix domain sockets for IPC communications when ncs-local-ipc/enabled configuration in ncs.conf is set to true. The main benefit of this communication method is that it is generally more secure than TCP sockets. It also provides additional information on the communicating peer, such as the user ID of the calling process. NSO can then use this information to authenticate the peer.

As part of the initial handshake, NSO reads the effective UID (euid) of the process initiating the Unix socket connection. The system then finds an /aaa/authentication/users/user entry with the corresponding uid value. Access is permitted or denied based on the local_ipc_access value. If access is permitted, the user connects as the user, found in the /aaa/authentication/users/user list. The following is an example of such a user list entry:

aaa authentication users user admin
 uid              500
 gid              500
 password         $6$...
 ssh_keydir       /var/ncs/homes/admin/.ssh
 homedir          /var/ncs/homes/admin
 local_ipc_access true
!

NSO will skip this access check in case the euid of the connecting process is 0 (root user) or same as the user NSO is running as. (In both these cases, the connecting user could access NSO data directly, bypassing the access check.)

If using Unix socket IPC, clients and client libraries must now specify the path that identifies the socket. The path must match the one set under ncs-local-ipc/path in ncs.conf. Clients may expose a client-specific way to set it, such as the -S option of the ncs_cli command. Alternatively, you can use the NCS_IPC_PATH environment variable to specify the socket path independently of the used client.

See $NCS_DIR/examples.ncs/security/ipc for a working example.

Group Membership

Once a user is authenticated, group membership must be established. A single user can be a member of several groups. Group membership is used by the authorization rules to decide which operations a certain user is allowed to perform. Thus the NSO AAA authorization model is entirely group-based. This is also sometimes referred to as role-based authorization.

All groups are stored under /nacm/groups, and each group contains a number of usernames. The ietf-netconf-acm.yang model defines a group entry:

list group {
  key name;

  description
    "One NACM Group Entry.  This list will only contain
     configured entries, not any entries learned from
     any transport protocols.";

  leaf name {
    type group-name-type;
    description
      "Group name associated with this entry.";
  }

  leaf-list user-name {
    type user-name-type;
    description
      "Each entry identifies the username of
       a member of the group associated with
       this entry.";
  }
}

The tailf-acm.yang model augments this with a gid leaf:

augment /nacm:nacm/nacm:groups/nacm:group {
  leaf gid {
    type int32;
    description
      "This leaf associates a numerical group ID with the group.
       When a OS command is executed on behalf of a user,
       supplementary group IDs are assigned based on 'gid' values
       for the groups that the use is a member of.";
  }
}

A valid group entry could thus look like:

<group>
  <name>admin</name>
  <user-name>bob</user-name>
  <user-name>joe</user-name>
  <gid xmlns="http://tail-f.com/yang/acm">99</gid>
</group>

The above XML data would then mean that users bob and joe are members of the admin group. The users need not necessarily exist as actual users under /aaa/authentication/users in order to belong to a group. If for example PAM authentication is used, it does not make sense to have all users listed under /aaa/authentication/users.

By default, the user is assigned to groups by using any groups provided by the northbound transport (e.g. via the ncs_cli or netconf-subsys programs), by consulting data under /nacm/groups, by consulting the /etc/group file, and by using any additional groups supplied by the authentication method. If /nacm/enable-external-groups is set to "false", only the data under /nacm/groups is consulted.

The resulting group assignment is the union of these methods, if it is non-empty. Otherwise, the default group is used, if configured ( /ncs-config/aaa/default-group in ncs.conf).

A user entry has a UNIX uid and UNIX gid assigned to it. Groups may have optional group IDs. When a user is logged in, and NSO tries to execute commands on behalf of that user, the uid/gid for the command execution is taken from the user entry. Furthermore, UNIX supplementary group IDs are assigned according to the gid's in the groups where the user is a member.

Authorization

Once a user is authenticated and group membership is established, when the user starts to perform various actions, each action must be authorized. Normally the authorization is done based on rules configured in the AAA data model as described in this section.

The authorization procedure first checks the value of /nacm/enable-nacm. This leaf has a default of true, but if it is set to false, all access is permitted. Otherwise, the next step is to traverse the rule-list list:

list rule-list {
  key "name";
  ordered-by user;
  description
    "An ordered collection of access control rules.";

  leaf name {
    type string {
      length "1..max";
    }
    description
      "Arbitrary name assigned to the rule-list.";
  }
  leaf-list group {
    type union {
      type matchall-string-type;
      type group-name-type;
    }
    description
      "List of administrative groups that will be
       assigned the associated access rights
       defined by the 'rule' list.

       The string '*' indicates that all groups apply to the
       entry.";
  }

  // ...
}

If the group leaf-list in a rule-list entry matches any of the user's groups, the cmdrule list entries are examined for command authorization, while the rule entries are examined for RPC, notification, and data authorization.

Command Authorization

The tailf-acm.yang module augments the rule-list entry in ietf-netconf-acm.yang with a cmdrule list:

augment /nacm:nacm/nacm:rule-list {

  list cmdrule {
    key "name";
    ordered-by user;
    description
      "One command access control rule. Command rules control access
       to CLI commands and Web UI functions.

       Rules are processed in user-defined order until a match is
       found.  A rule matches if 'context', 'command', and
       'access-operations' match the request.  If a rule
       matches, the 'action' leaf determines if access is granted
       or not.";

    leaf name {
      type string {
        length "1..max";
      }
      description
        "Arbitrary name assigned to the rule.";
    }

    leaf context {
      type union {
        type nacm:matchall-string-type;
        type string;
      }
      default "*";
      description
        "This leaf matches if it has the value '*' or if its value
         identifies the agent that is requesting access, i.e. 'cli'
         for CLI or 'webui' for Web UI.";
    }

    leaf command {
      type string;
      default "*";
      description
        "Space-separated tokens representing the command. Refer
         to the Tail-f AAA documentation for further details.";
    }

    leaf access-operations {
      type union {
        type nacm:matchall-string-type;
        type nacm:access-operations-type;
      }
      default "*";
      description
        "Access operations associated with this rule.

         This leaf matches if it has the value '*' or if the
         bit corresponding to the requested operation is set.";
    }

    leaf action {
      type nacm:action-type;
      mandatory true;
      description
        "The access control action associated with the
         rule.  If a rule is determined to match a
         particular request, then this object is used
         to determine whether to permit or deny the
         request.";
    }

    leaf log-if-permit {
      type empty;
      description
        "If this leaf is present, access granted due to this rule
         is logged in the developer log. Otherwise, only denied
         access is logged. Mainly intended for debugging of rules.";
    }

    leaf comment {
      type string;
      description
        "A textual description of the access rule.";
    }
  }
}

Each rule has seven leafs. The first is the name list key, the following three leafs are matching leafs. When NSO tries to run a command, it tries to match the command towards the matching leafs and if all of context, command, and access-operations match, the fifth field, i.e. the action, is applied.

  • name: name is the name of the rule. The rules are checked in order, with the ordering given by the YANG ordered-by user semantics, i.e. independent of the key values.

  • context: context is either of the strings cli, webui, or * for a command rule. This means that we can differentiate authorization rules for which access method is used. Thus if command access is attempted through the CLI, the context will be the string cli whereas for operations via the Web UI, the context will be the string webui.

  • command: This is the actual command getting executed. If the rule applies to one or several CLI commands, the string is a space-separated list of CLI command tokens, for example request system reboot. If the command applies to Web UI operations, it is a space-separated string similar to a CLI string. A string that consists of just * matches any command. In general, we do not recommend using command rules to protect the configuration. Use rules for data access as described in the next section to control access to different parts of the data. Command rules should be used only for CLI commands and Web UI operations that cannot be expressed as data rules. The individual tokens can be POSIX extended regular expressions. Each regular expression is implicitly anchored, i.e. an ^ is prepended and a $ is appended to the regular expression.

  • access-operations: access-operations is used to match the operation that NSO tries to perform. It must be one or both of the "read" and "exec" values from the access-operations-type bits type definition in ietf-netconf-acm.yang, or "*" to match any operation.

  • action: If all of the previous fields match, the rule as a whole matches and the value of action will be taken. I.e. if a match is found, a decision is made whether to permit or deny the request in its entirety. If action is permit, the request is permitted, if action is deny, the request is denied and an entry is written to the developer log.

  • log-if-permit: If this leaf is present, an entry is written to the developer log for a matching request also when action is permit. This is very useful when debugging command rules.

  • comment: An optional textual description of the rule.

For the rule processing to be written to the devel log, the /ncs-config/logs/developer-log-level entry in ncs.conf must be set to trace.

If no matching rule is found in any of the cmdrule lists in any rule-list entry that matches the user's groups, this augmentation from tailf-acm.yang is relevant:

augment /nacm:nacm {
  leaf cmd-read-default {
    type nacm:action-type;
    default "permit";
    description
      "Controls whether command read access is granted
       if no appropriate cmdrule is found for a
       particular command read request.";
  }

  leaf cmd-exec-default {
    type nacm:action-type;
    default "permit";
    description
      "Controls whether command exec access is granted
       if no appropriate cmdrule is found for a
       particular command exec request.";
  }

  leaf log-if-default-permit {
    type empty;
    description
      "If this leaf is present, access granted due to one of
       /nacm/read-default, /nacm/write-default, or /nacm/exec-default
       /nacm/cmd-read-default, or /nacm/cmd-exec-default
       being set to 'permit' is logged in the developer log.
       Otherwise, only denied access is logged. Mainly intended
       for debugging of rules.";
  }
}
  • If read access is requested, the value of /nacm/cmd-read-default determines whether access is permitted or denied.

  • If exec access is requested, the value of /nacm/cmd-exec-default determines whether access is permitted or denied.

If access is permitted due to one of these default leafs, the /nacm/log-if-default-permithas the same effect as the log-if-permit leaf for the cmdrule lists.

RPC, Notification, and Data Authorization

The rules in the rule list are used to control access to rpc operations, notifications, and data nodes defined in YANG models. Access to invocation of actions (tailf:action) is controlled with the same method as access to data nodes, with a request for exec access. ietf-netconf-acm.yang defines a rule entry as:

list rule {
  key "name";
  ordered-by user;
  description
    "One access control rule.

     Rules are processed in user-defined order until a match is
     found.  A rule matches if 'module-name', 'rule-type', and
     'access-operations' match the request.  If a rule
     matches, the 'action' leaf determines if access is granted
     or not.";

  leaf name {
    type string {
      length "1..max";
    }
    description
      "Arbitrary name assigned to the rule.";
  }

  leaf module-name {
    type union {
      type matchall-string-type;
      type string;
    }
    default "*";
    description
      "Name of the module associated with this rule.

       This leaf matches if it has the value '*' or if the
       object being accessed is defined in the module with the
       specified module name.";
  }
  choice rule-type {
    description
      "This choice matches if all leafs present in the rule
       match the request.  If no leafs are present, the
       choice matches all requests.";
    case protocol-operation {
      leaf rpc-name {
        type union {
          type matchall-string-type;
          type string;
        }
        description
          "This leaf matches if it has the value '*' or if
           its value equals the requested protocol operation
           name.";
      }
    }
    case notification {
      leaf notification-name {
        type union {
          type matchall-string-type;
          type string;
        }
        description
          "This leaf matches if it has the value '*' or if its
           value equals the requested notification name.";
      }
    }
    case data-node {
      leaf path {
        type node-instance-identifier;
        mandatory true;
        description
          "Data Node Instance Identifier associated with the
           data node controlled by this rule.

           Configuration data or state data instance
           identifiers start with a top-level data node.  A
           complete instance identifier is required for this
           type of path value.

           The special value '/' refers to all possible
           data-store contents.";
      }
    }
  }

  leaf access-operations {
    type union {
      type matchall-string-type;
      type access-operations-type;
    }
    default "*";
    description
      "Access operations associated with this rule.

       This leaf matches if it has the value '*' or if the
       bit corresponding to the requested operation is set.";
  }

  leaf action {
    type action-type;
    mandatory true;
    description
      "The access control action associated with the
       rule.  If a rule is determined to match a
       particular request, then this object is used
       to determine whether to permit or deny the
       request.";
  }

  leaf comment {
    type string;
    description
      "A textual description of the access rule.";
  }
}

tailf-acm augments this with two additional leafs:

augment /nacm:nacm/nacm:rule-list/nacm:rule {

  leaf context {
    type union {
      type nacm:matchall-string-type;
      type string;
    }
    default "*";
    description
      "This leaf matches if it has the value '*' or if its value
       identifies the agent that is requesting access, e.g. 'netconf'
       for NETCONF, 'cli' for CLI, or 'webui' for Web UI.";

  }

  leaf log-if-permit {
    type empty;
    description
      "If this leaf is present, access granted due to this rule
       is logged in the developer log. Otherwise, only denied
       access is logged. Mainly intended for debugging of rules.";
  }
}

Similar to the command access check, whenever a user through some agent tries to access an RPC, a notification, a data item, or an action, access is checked. For a rule to match, three or four leafs must match and when a match is found, the corresponding action is taken.

We have the following leafs in the rule list entry.

  • name: The name of the rule. The rules are checked in order, with the ordering given by the YANG ordered-by user semantics, i.e. independent of the key values.

  • module-name: The module-name string is the name of the YANG module where the node being accessed is defined. The special value * (i.e. the default) matches all modules.\

Since the elements of the path to a given node may be defined in different YANG modules when augmentation is used, rules that have a value other than `*` for the `module-name` leaf may require that additional processing is done before a decision to permit or deny, or the access can be taken. Thus if an XPath that completely identifies the nodes that the rule should apply to is given for the `path` leaf (see below), it may be best to leave the `module-name` leaf unset.
  • rpc-name / notification-name / path: This is a choice between three possible leafs that are used for matching, in addition to the module-name:

  • rpc-name: The name of an RPC operation, or * to match any RPC.

  • notification-name: the name of a notification, or * to match any notification.

  • path: A restricted XPath expression leading down into the populated XML tree. A rule with a path specified matches if it is equal to or shorter than the checked path. Several types of paths are allowed.

    1. Tagpaths that do not contain any keys. For example /ncs/live-device/live-status.

    2. Instantiated key: as in /devices/device[name="x1"]/config/interface matches the interface configuration for managed device "x1" It's possible to have partially instantiated paths only containing some keys instantiated - i.e. combinations of tagpaths and keypaths. Assuming a deeper tree, the path /devices/device/config/interface[name="eth0"] matches the eth0 interface configuration on all managed devices.

    3. The wild card at the end as in: /services/web-site/* does not match the website service instances, but rather all children of the website service instances.\

    Thus, the path in a rule is matched against the path in the attempted data access. If the attempted access has a path that is equal to or longer than the rule path - we have a match. If none of the leafs rpc-name, notification-name, or path are set, the rule matches for any RPC, notification, data, or action access.

  • context: context is either of the strings cli, netconf, webui, snmp, or * for a data rule. Furthermore, when we initiate user sessions from MAAPI, we can choose any string we want. Similarly to command rules, we can differentiate access depending on which agent is used to gain access.

  • access-operations: access-operations is used to match the operation that NSO tries to perform. It must be one or more of the "create", "read", "update", "delete" and "exec" values from the access-operations-type bits type definition in ietf-netconf-acm.yang, or "*" to match any operation.

  • action: This leaf has the same characteristics as the action leaf for command access.

  • log-if-permit: This leaf has the same characteristics as the log-if-permit leaf for command access.

  • comment: An optional textual description of the rule.

If no matching rule is found in any of the rule lists in any rule-list entry that matches the user's groups, the data model node for which access is requested is examined for the presence of the NACM extensions:

  • If the nacm:default-deny-all extension is specified for the data model node, the access is denied.

  • If the nacm:default-deny-write extension is specified for the data model node, and create, update, or delete access is requested, the access is denied.

If examination of the NACM extensions did not result in access being denied, the value (permit or deny) of the relevant default leaf is examined:

  • If read access is requested, the value of /nacm/read-default determines whether access is permitted or denied.

  • If create, update, or delete access is requested, the value of /nacm/write-default determines whether access is permitted or denied.

  • If exec access is requested, the value of /nacm/exec-default determines whether access is permitted or denied.

If access is permitted due to one of these default leafs, this augmentation from tailf-acm.yang is relevant:

augment /nacm:nacm {
  ...
  leaf log-if-default-permit {
    type empty;
    description
      "If this leaf is present, access granted due to one of
       /nacm/read-default, /nacm/write-default, /nacm/exec-default
       /nacm/cmd-read-default, or /nacm/cmd-exec-default
       being set to 'permit' is logged in the developer log.
       Otherwise, only denied access is logged. Mainly intended
       for debugging of rules.";
  }
}

I.e. it has the same effect as the log-if-permit leaf for the rule lists, but for the case where the value of one of the default leafs permits access.

When NSO executes a command, the command rules in the authorization database are searched, The rules are tried in order, as described above. When a rule matches the operation (command) that NSO is attempting, the action of the matching rule is applied - whether permit or deny.

When actual data access is attempted, the data rules are searched. E.g. when a user attempts to execute delete aaa in the CLI, the user needs delete access to the entire tree /aaa.

Another example is if a CLI user writes show configuration aaa TAB it suffices to have read access to at least one item below /aaa for the CLI to perform the TAB completion. If no rule matches or an explicit deny rule is found, the CLI will not TAB complete.

Yet another example is if a user tries to execute delete aaa authentication users, we need to perform a check on the paths /aaa and /aaa/authentication before attempting to delete the sub-tree. Say that we have a rule for path /aaa/authentication/users which is a permit rule and we have a subsequent rule for path /aaa which is a deny rule. With this rule set the user should indeed be allowed to delete the entire /aaa/authentication/users tree but not the /aaa tree nor the /aaa/authentication tree.

We have two variations on how the rules are processed. The easy case is when we actually try to read or write an item in the configuration database. The execution goes like this:

foreach rule {
    if (match(rule, path)) {
       return rule.action;
    }
}

The second case is when we execute TAB completion in the CLI. This is more complicated. The execution goes like this:

rules = select_rules_that_may_match(rules, path);
if (any_rule_is_permit(rules))
    return permit;
else
    return deny;

The idea is that as we traverse (through TAB) down the XML tree, as long as there is at least one rule that can possibly match later, once we have more data, we must continue. For example, assume we have:

  1. "/system/config/foo" --> permit

  2. "/system/config" --> deny

If we in the CLI stand at "/system/config" and hit TAB we want the CLI to show foo as a completion, but none of the other nodes that exist under /system/config. Whereas if we try to execute delete /system/config the request must be rejected.

By default, NACM rules are configured for the entire tailf:action or YANG 1.1 action statements, but not for input statement child leafs. To override this behavior, and enable NACM rules on input leafs, set the following parameter to 'true': /ncs-config/aaa/action-input-rules/enabled. When enabled all action input leafs given to an action will be validated for NACM rules. If broad 'deny' NACM rules are used, you might need to add 'permit' rules for the affected action input leafs to allow actions to be used with parameters.

NACM Rules and Services

By design NACM rules are ignored for changes done by services - FASTMAP, Reactive FASTMAP, or Nano services. The reasoning behind this is that a service package can be seen as a controlled way to provide limited access to devices for a user group that is not allowed to apply arbitrary changes on the devices.

However, there are NSO installations where this behavior is not desired, and NSO administrators want to enforce NACM rules even on changes done by services. For this purpose, the leaf called /nacm/enforce-nacm-on-services is provided. By default, it is set to false.

Note however that currently, even with this leaf set to true, there are limitations. Namely, the post-actions for nano-services are run in a user session without any access checks. Besides that, NACM rules are not enforced on the read operations performed in the service callbacks.

It might be desirable to deny everything for a user group and only allow access to a specific service. This pattern could be used to allow an operator to provision the service, but deny everything else. While this pattern works for a normal FASTMAP service, there are some caveats for stacked services, Reactive FASTMAP, and Nano services. For these kinds of services, in addition to the service itself, access should be provided to the user group for the following paths:

  • In case of stacked services, the user group needs read and write access to the leaf private/re-deploy-counter under the bottom service. Otherwise, the user will not be able to redeploy the service.

  • In the case of Reactive FASTMAP or Nano services, the user group needs read and write access to the following:

    • /zombies

    • /side-effect-queue

    • /kickers

Device Group Authorization

In deployments with many devices, it can become cumbersome to handle data authorization per device. To help with this there is a rule type that works on device group membership (for more on device groups, see Device Groups). To do this, devices are added to different device groups, and the rule type device-group-rule is used.

The IETF NACM rule type is augmented with a new rule type named device-group-rule which contains a leafref to the device groups. See the following example.

Device Group Model Augmentation
augment "/nacm:nacm/nacm:rule-list/nacm:rule/nacm:rule-type" {
  case device-group-rule {
    leaf device-group {
      type leafref {
        path "/ncs:devices/ncs:device-group/ncs:name";
      }
      description
        "Which device group this rule applies to.";
    }
  }
}

In the example below, we configure two device groups based on different regions and add devices to them.

Device Group Configuration
<devices>
  <device-group>
    <name>us_east</name>
    <device-name>cli0</device-name>
    <device-name>gen0</device-name>
  </device-group>
  <device-group>
    <name>us_west</name>
    <device-name>nc0</device-name>
  </device-group>
</devices>

In the example below, we configure an operator for the us_east region:

NACM Group Configuration
<nacm>
  <groups>
    <group>
      <name>us_east</name>
      <user-name>us_east_oper</user-name>
    </group>
  </groups>
</nacm>

In the example below, we configure the device group rules and refer to the device group and the us_east group.

Device Group Authorization Rules
<nacm>
  <rule-list>
    <name>us_east</name>
    <group>us_east</group>
    <rule>
      <name>us_east_read_permit</name>
      <device-group xmlns="http://tail-f.com/yang/ncs-acm/device-group-authorization">us_east</device-group>
      <access-operations>read</access-operations>
      <action>permit</action>
    </rule>
    <rule>
      <name>us_east_create_permit</name>
      <device-group xmlns="http://tail-f.com/yang/ncs-acm/device-group-authorization">us_east</device-group>
      <access-operations>create</access-operations>
      <action>permit</action>
    </rule>
    <rule>
      <name>us_east_update_permit</name>
      <device-group xmlns="http://tail-f.com/yang/ncs-acm/device-group-authorization">us_east</device-group>
      <access-operations>update</access-operations>
      <action>permit</action>
    </rule>
    <rule>
      <name>us_east_delete_permit</name>
      <device-group xmlns="http://tail-f.com/yang/ncs-acm/device-group-authorization">us_east</device-group>
      <access-operations>delete</access-operations>
      <action>permit</action>
    </rule>
  </rule-list>
</nacm>

In summary device group authorization gives a more compact configuration for deployments where devices can be grouped and authorization can be done on a device group basis.

Modifications on the device-group subtree are recommended to be controlled by a limited set of users.

Authorization Examples

Assume that we have two groups, admin and oper. We want admin to be able to see and edit the XML tree rooted at /aaa, but we do not want users who are members of the oper group to even see the /aaa tree. We would have the following rule list and rule entries. Note, here we use the XML data from tailf-aaa.yang to exemplify. The examples apply to all data, for all data models loaded into the system.

<rule-list>
  <name>admin</name>
  <group>admin</group>
  <rule>
    <name>tailf-aaa</name>
    <module-name>tailf-aaa</module-name>
    <path>/</path>
    <access-operations>read create update delete</access-operations>
    <action>permit</action>
  </rule>
</rule-list>
<rule-list>
  <name>oper</name>
  <group>oper</group>
  <rule>
    <name>tailf-aaa</name>
    <module-name>tailf-aaa</module-name>
    <path>/</path>
    <access-operations>read create update delete</access-operations>
    <action>deny</action>
  </rule>
</rule-list>

If we do not want the members of oper to be able to execute the NETCONF operation edit-config, we define the following rule list and rule entries:

<rule-list>
  <name>oper</name>
  <group>oper</group>
  <rule>
    <name>edit-config</name>
    <rpc-name>edit-config</rpc-name>
    <context xmlns="http://tail-f.com/yang/acm">netconf</context>
    <access-operations>exec</access-operations>
    <action>deny</action>
  </rule>
</rule-list>

To spell it out, the above defines four elements to match. If NSO tries to perform a netconf operation, which is the operation edit-config, and the user who runs the command is a member of the oper group, and finally it is an exec (execute) operation, we have a match. If so, the action is deny.

The path leaf can be used to specify explicit paths into the XML tree using XPath syntax. For example the following:

<rule-list>
  <name>admin</name>
  <group>admin</group>
  <rule>
    <name>bob-password</name>
    <path>/aaa/authentication/users/user[name='bob']/password</path>
    <context xmlns="http://tail-f.com/yang/acm">cli</context>
    <access-operations>read update</access-operations>
    <action>permit</action>
  </rule>
</rule-list>

Explicitly allows the admin group to change the password for precisely the bob user when the user is using the CLI. Had path been /aaa/authentication/users/user/password the rule would apply to all password elements for all users. Since the path leaf completely identifies the nodes that the rule applies to, we do not need to give tailf-aaa for the module-name leaf.

NSO applies variable substitution, whereby the username of the logged-in user can be used in a path. Thus:

<rule-list>
  <name>admin</name>
  <group>admin</group>
  <rule>
    <name>user-password</name>
    <path>/aaa/authentication/users/user[name='$USER']/password</path>
    <context xmlns="http://tail-f.com/yang/acm">cli</context>
    <access-operations>read update</access-operations>
    <action>permit</action>
  </rule>
</rule-list>

The above rule allows all users that are part of the admin group to change their own passwords only.

A member of oper is able to execute NETCONF operation action if that member has exec access on NETCONF RPC action operation, read access on all instances in the hierarchy of data nodes that identifies the specific action in the data store, and exec access on the specific action. For example, an action is defined as below.

container test {
  action double {
    input {
      leaf number {
        type uint32;
      }
    }
    output {
      leaf result {
        type uint32;
      }
    }
  }
}

To be able to execute double action through NETCONF RPC, the members of oper need the following rule list and rule entries.

<rule-list>
  <name>oper</name>
  <group>oper</group>

  <rule>
    <name>allow-netconf-rpc-action</name>
    <rpc-name>action</rpc-name>
    <context xmlns="http://tail-f.com/yang/acm">netconf</context>
    <access-operations>exec</access-operations>
    <action>permit</action>
  </rule>
  <rule>
    <name>allow-read-test</name>
    <path>/test</path>
    <access-operations>read</access-operations>
    <action>permit</action>
  </rule>
  <rule>
    <name>allow-exec-double</name>
    <path>/test/double</path>
    <access-operations>exec</access-operations>
    <action>permit</action>
  </rule>
</rule-list>

Or, a simpler rule set as the following.

<rule-list>
  <name>oper</name>
  <group>oper</group>

  <rule>
    <name>allow-netconf-rpc-action</name>
    <rpc-name>action</rpc-name>
    <context xmlns="http://tail-f.com/yang/acm">netconf</context>
    <access-operations>exec</access-operations>
    <action>permit</action>
  </rule>
  <rule>
    <name>allow-exec-double</name>
    <path>/test</path>
    <access-operations>read exec</access-operations>
    <action>permit</action>
  </rule>
</rule-list>

Finally, if we wish members of the oper group to never be able to execute the request system reboot command, also available as a reboot NETCONF rpc, we have:

<rule-list>
  <name>oper</name>
  <group>oper</group>

  <cmdrule xmlns="http://tail-f.com/yang/acm">
    <name>request-system-reboot</name>
    <context>cli</context>
    <command>request system reboot</command>
    <access-operations>exec</access-operations>
    <action>deny</action>
  </cmdrule>

  <!-- The following rule is required since the user can -->
  <!-- do "edit system" -->

  <cmdrule xmlns="http://tail-f.com/yang/acm">
    <name>request-reboot</name>
    <context>cli</context>
    <command>request reboot</command>
    <access-operations>exec</access-operations>
    <action>deny</action>
  </cmdrule>

  <rule>
    <name>netconf-reboot</name>
    <rpc-name>reboot</rpc-name>
    <context xmlns="http://tail-f.com/yang/acm">netconf</context>
    <access-operations>exec</access-operations>
    <action>deny</action>
  </rule>

</rule-list>

Troubleshooting NACM Rules

In this section, we list some tips to make it easier to troubleshoot NACM rules.

Use log-if-permit and log-if-default-permit together with the developer log level set to trace.

Use the tailf-acm.yang module augmentation log-if-permit leaf for rules with action permit. When those rules trigger a permit action a trace entry is added to the developer log. To see trace entries make sure the /ncs-config/logs/developer-log-level is set to trace.

If you have a default rule with action permit you can use the log-if-default-permit leaf instead.

NACM rules are read at the start of the session and are used throughout the session.

When a user session is created it will gather the authorization rules that are relevant for that user's group(s). The rules are used throughout the user session lifetime. When we update the AAA rules the active sessions are not affected. For example, if an administrator updates the NACM rules in one session the update will not apply to any other currently active sessions. The updates will apply to new sessions created after the update.

Explicitly state NACM groups when starting the CLI. For example ncs_cli -u oper -g oper.

It is the user's group membership that determines what rules apply. Starting the CLI using the ncs_cli command without explicitly setting the groups, defaults to the actual UNIX groups the user is a member of. On Darwin, one of the default groups is usually admin, which can lead to the wrong group being used.

Be careful with namespaces in rulepaths.

Unless a rulepath is made explicit by specifying namespace it will apply to that specific path in all namespaces. Below we show parts of an example from RFC 8341, where the path element has an xmlns attribute and the path is namespaced. If these would not have been namespaced, the rules would not behave as expected.

Example: Excerpt from RFC 8341 Appendix A.4
         <rule>
           <name>permit-acme-config</name>
           <path xmlns:acme="http://example.com/ns/netconf">
             /acme:acme-netconf/acme:config-parameters
           </path>
         ...

In the example above (Excerpt from RFC 8341 Appendix A.4), the path is namespaced.

The AAA Cache

NSO's AAA subsystem will cache the AAA information in order to speed up the authorization process. This cache must be updated whenever there is a change to the AAA information. The mechanism for this update depends on how the AAA information is stored, as described in the following two sections.

Populating AAA using CDB

To start NSO, the data models for AAA must be loaded. The defaults in the case that no actual data is loaded for these models allow all read and exec access, while write access is denied. Access may still be further restricted by the NACM extensions, though - e.g. the /nacm container has nacm:default-deny-all, meaning that not even read access is allowed if no data is loaded.

The NSO installation ships with an XML initialization file containing AAA configuration. The file is called aaa_init.xml and is, by default, copied to the CDB directory by the NSO install scripts.

The local installation variant, targeting development only, defines two users, admin and oper with passwords set to admin and oper respectively for authentication. The two users belong to user groups with NACM rules restricting their authorization level. The system installation aaa_init.xml variant, targeting production deployment, defines NACM rules only as users are, by default, authenticated using PAM. The NACM rules target two user groups, ncsadmin and ncsoper. Users belonging to the ncsoper group are limited to read-only access.

The default aaa_init.xml file provided with the NSO system installation must not be used as-is in a deployment without reviewing and verifying that every NACM rule in the file matches

Normally the AAA data will be stored as configuration in CDB. This allows for changes to be made through NSO's transaction-based configuration management. In this case, the AAA cache will be updated automatically when changes are made to the AAA data. If changing the AAA data via NSO's configuration management is not possible or desirable, it is alternatively possible to use the CDB operational data store for AAA data. In this case, the AAA cache can be updated either explicitly e.g. by using the maapi_aaa_reload() function, see the confd_lib_maapi(3) in the Manual Pages manual page, or by triggering a subscription notification by using the subscription lock when updating the CDB operational data store, see Using CDB in Development.

Hiding the AAA Tree

Some applications may not want to expose the AAA data to end users in the CLI or the Web UI. Two reasonable approaches exist here and both rely on the tailf:export statement. If a module has tailf:export none it will be invisible to all agents. We can then either use a transform whereby we define another AAA model and write a transform program that maps our AAA data to the data that must exist in tailf-aaa.yang and ietf-netconf-acm.yang. This way we can choose to export and and expose an entirely different AAA model.

Yet another very easy way out, is to define a set of static AAA rules whereby a set of fixed users and fixed groups have fixed access to our configuration data. Possibly the only field we wish to manipulate is the password field.

Last updated

© Copyright 2024 Cisco Systems, Inc. | This site is part of the official Cisco Crosswork NSO documentation set.