# AAA Infrastructure

## 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 <a href="#d5e5697" id="d5e5697"></a>

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](https://tools.ietf.org/html/rfc8341)) as augmented by `tailf-acm.yang` is used for group assignment and authorization.

### Data Model Contents <a href="#d5e5706" id="d5e5706"></a>

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` <a href="#d5e5722" id="d5e5722"></a>

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](#ug.aaa.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](#ug.aaa.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](#ug.aaa.external_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](#ug.aaa.external_challenge).
* `/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](#ug.aaa.packageauth).
* `/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 <a href="#ug.aaa.authentication" id="ug.aaa.authentication"></a>

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](#ug.aaa.groups), and performs authorization, as described in [Authorization](#ug.aaa.authorization).

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

```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](https://nso-docs.cisco.com/guides/nso-6.3/development/core-concepts/northbound-apis#ug.netconf_agent.transport) in Northbound APIs.
* When NETCONF uses TCP, as described in [NETCONF Transport Protocols](https://nso-docs.cisco.com/guides/nso-6.3/development/core-concepts/northbound-apis#ug.netconf_agent.transport) 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](#authenticating-ipc-access).
* When SNMP is used, SNMP has its own authentication mechanisms. See [NSO SNMP Agent](https://nso-docs.cisco.com/guides/nso-6.3/development/core-concepts/northbound-apis#the-nso-snmp-agent) in Northbound APIs.
* When the method `Maapi.startUserSession()` is used without a preceding call of `Maapi.authenticate()`.

### Public Key Login <a href="#ug.aaa.public_key_login" id="ug.aaa.public_key_login"></a>

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](#ug.aaa.groups).

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:

```bash
# 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:

```xml
<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`.

{% hint style="info" %}
Since the release of [OpenSSH 7.0](https://www.openssh.com/txt/release-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](https://www.openssh.com/legacy.html) webpage.
{% endhint %}

### Password Login <a href="#d5e5901" id="d5e5901"></a>

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)](https://nso-docs.cisco.com/guides/nso-6.3/resources/index/section5#ncs.conf) 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](#ug.aaa.pam) for details.
3. If all of the above fails and external authentication is enabled, the configured executable is invoked. See [External Authentication](#ug.aaa.external_authentication) for details.

If authentication succeeds, the user's group membership is established as described in [Group Membership](#ug.aaa.groups).

### PAM <a href="#ug.aaa.pam" id="ug.aaa.pam"></a>

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.

{% hint style="info" %}
PAM is the recommended way to authenticate NSO users.
{% endhint %}

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.

   ```xml
   <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.

   ```bash
   # 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:

```bash
# 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 href="#ug.aaa.external_authentication" id="ug.aaa.external_authentication"></a>

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.

{% hint style="info" %}
There is a general limit of 16000 bytes of output from the `externalauth` program.
{% endhint %}

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"`

{% hint style="info" %}
The challenge-prompt may be multi-line, why it must be base64 encoded.
{% endhint %}

For more information on multi-factor authentication, see [External Multi-Factor Authentication](#ug.aaa.external_challenge).

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 <a href="#ug.aaa.external_validation" id="ug.aaa.external_validation"></a>

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.

{% hint style="info" %}
There is a general limit of 16000 bytes of output from the `externalvalidation` program.
{% endhint %}

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"`

{% hint style="info" %}
The challenge prompt may be multi-line, why it must be base64 encoded.
{% endhint %}

For more information on multi-factor authentication, see [External Multi-Factor Authentication](#ug.aaa.external_challenge).

### External Multi-Factor Authentication <a href="#ug.aaa.external_challenge" id="ug.aaa.external_challenge"></a>

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.

{% hint style="info" %}
There is a general limit of 16000 bytes of output from the `externalchallenge` program.
{% endhint %}

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"`

{% hint style="info" %}
The challenge prompt may be multi-line, so it must be base64 encoded.
{% endhint %}

{% hint style="info" %}
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.
{% endhint %}

{% hint style="info" %}
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.
{% endhint %}

### Package Authentication <a href="#ug.aaa.packageauth" id="ug.aaa.packageauth"></a>

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.

{% hint style="info" %}
Package authentication is supported for Single Sign-On (see [Single Sign-On](https://nso-docs.cisco.com/guides/nso-6.3/development/advanced-development/web-ui-development#single-sign-on-sso) 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!
{% endhint %}

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)](https://nso-docs.cisco.com/guides/nso-6.3/resources/index/section5#ncs.conf) 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"`.

{% hint style="info" %}
The fields user, password, orig\_request, request, headers, and body are all base64 encoded.
{% endhint %}

{% hint style="info" %}
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==`'.
{% endhint %}

{% hint style="info" %}
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`.
{% endhint %}

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.

{% hint style="info" %}
There is a general limit of 16000 bytes of output from the "packageauth" program.
{% endhint %}

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](#ug.aaa.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. Base64 encoded.
* `$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.

{% hint style="info" %}
When serving a username/password request, script output other than accept, challenge or abort will be treated as if authentication failed.
{% endhint %}

### **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.

{% hint style="info" %}
The fields `challengeid` and response are base64 encoded when passed to the script.
{% endhint %}

## Authenticating IPC Access

NSO communicates with clients (client libraries, **ncs\_cli**, and similar) 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:

```bash
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. NSO implements it as an access check, where every IPC client must prove that it has access to a pre-shared key. See [Restricting Access to the IPC Port](https://nso-docs.cisco.com/guides/nso-6.3/advanced-topics/ipc-ports#ug.ncs_advanced.ipc.restricting) on how to enable it.

## Group Membership <a href="#ug.aaa.groups" id="ug.aaa.groups"></a>

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:

```yang
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:

```yang
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:

```xml
<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 <a href="#ug.aaa.authorization" id="ug.aaa.authorization"></a>

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:

```yang
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 <a href="#d5e6440" id="d5e6440"></a>

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

```yang
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:

```yang
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-permit`has the same effect as the `log-if-permit` leaf for the `cmdrule` lists.

### RPC, Notification, and Data Authorization <a href="#d5e6526" id="d5e6526"></a>

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:

```yang
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:

```yang
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.\\

{% hint style="info" %}

```
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.
```

{% endhint %}

* `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:

```yang
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 <a href="#d5e6693" id="d5e6693"></a>

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 <a href="#d5e6700" id="d5e6700"></a>

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](https://nso-docs.cisco.com/guides/nso-6.3/operation-and-usage/operations/nso-device-manager#user_guide.devicemanager.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.

{% code title="Device Group Model Augmentation" %}

```yang
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.";
    }
  }
}
```

{% endcode %}

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

{% code title="Device Group Configuration" %}

```xml
<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>
```

{% endcode %}

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

{% code title="NACM Group Configuration" %}

```xml
<nacm>
  <groups>
    <group>
      <name>us_east</name>
      <user-name>us_east_oper</user-name>
    </group>
  </groups>
</nacm>
```

{% endcode %}

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

{% code title="Device Group Authorization Rules" %}

```xml
<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>
```

{% endcode %}

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 <a href="#d5e6730" id="d5e6730"></a>

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.

```xml
<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:

```xml
<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:

```xml
<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:

```xml
<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.

```yang
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.

```xml
<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.

```xml
<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:

```xml
<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.

{% hint style="success" %}
Use `log-if-permit` and `log-if-default-permit` together with the developer log level set to `trace`.
{% endhint %}

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.

{% hint style="success" %}
NACM rules are read at the start of the session and are used throughout the session.
{% endhint %}

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.

{% hint style="success" %}
Explicitly state NACM groups when starting the CLI. For example `ncs_cli -u oper -g oper`.
{% endhint %}

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.

{% hint style="success" %}
Be careful with namespaces in rulepaths.
{% endhint %}

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](https://tools.ietf.org/html/rfc8341), 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.

{% code title="Example: Excerpt from RFC 8341 Appendix A.4" %}

```xml
         <rule>
           <name>permit-acme-config</name>
           <path xmlns:acme="http://example.com/ns/netconf">
             /acme:acme-netconf/acme:config-parameters
           </path>
         ...
```

{% endcode %}

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

## The AAA Cache <a href="#d5e6799" id="d5e6799"></a>

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 <a href="#d5e6802" id="d5e6802"></a>

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.

{% hint style="info" %}
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
{% endhint %}

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)](https://nso-docs.cisco.com/guides/nso-6.3/resources/index/section3#confd_lib_maapi) 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](https://nso-docs.cisco.com/guides/nso-6.3/development/core-concepts/using-cdb) in Development.

### Hiding the AAA Tree <a href="#d5e6817" id="d5e6817"></a>

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.
