Chapter 14. The AAA infrastructure

Table of Contents

14.1. The problem
14.2. Structure - data models
14.3. AAA related items in confd.conf
14.4. Authentication
14.5. Group Membership
14.6. Authorization
14.7. The AAA cache
14.8. Populating AAA using CDB
14.9. Populating AAA using external data
14.10. Hiding the AAA tree

14.1. The problem

This chapter describes how to use ConfD's built-in authentication and authorization mechanisms. Users log into ConfD through the CLI, NETCONF, 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 in order to gain access.

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 authorization of commands and authorization of data access.

14.2. Structure - data models

The ConfD daemon manages device configuration including AAA information. In fact, ConfD both manages AAA information and uses it. The AAA information describes which users may login, what passwords they have and what they are allowed to do.

This is solved in ConfD by requiring a data model to be both loaded and populated with data. ConfD uses the YANG module tailf-aaa.yang for authentication, while ietf-netconf-acm.yang (NACM, RFC 6536) as augmented by tailf-acm.yang is used for group assignment and authorization. For backwards compatibility, it is alternatively possible to use the older revision 2011-09-22 of tailf-aaa.yang for all of authentication, group assignment, and authorization. This legacy version of tailf-aaa can be found in the $CONFD_DIR/src/confd/aaa directory, but its usage is not further described here. Detailed information about this can be found in versions of this document for ConfD-5.3 and earlier.

14.2.1. 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 ConfD, in particular support for 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 $CONFD_DIR/src/confd/yang directory in the release, while tailf-aaa.yang can be found in the $CONFD_DIR/src/confd/aaa directory.

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 ConfD 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 ConfD itself uses them. The data belongs to the application, but ConfD itself is the user of the data.

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

14.3. AAA related items in confd.conf

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

/confdConfig/aaa/sshServerKeyDir

If SSH termination is enabled for NETCONF or the CLI, the ConfD built-in SSH server needs to have server keys. These keys are generated by the ConfD install script and by default end up in $CONFD_DIR/etc/confd/ssh .

It is also possible to use OpenSSH to terminate NETCONF or the CLI. If OpenSSH is used to terminate SSH traffic, the SSH keys are not necessary.

/confdConfig/aaa/sshPubkeyAuthentication

If SSH termination is enabled for NETCONF or the CLI, this item controls how the ConfD SSH daemon locates the user keys for public key authentication. See Section 14.4.1, “Public Key Login” for the details.

/confdConfig/aaa/localAuthentication/enabled

The term "local user" refers to a user stored under /aaa/authentication/users. The alternative is a user unknown to ConfD, typically authenticated by PAM.

By default, ConfD 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 login 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 ConfD.

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 auth" also /nacm/groups (for NACM) or /aaa/authentication/groups (for legacy tailf-aaa) empty.

/confdConfig/aaa/pam

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

/confdConfig/aaa/defaultGroup

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.

/confdConfig/aaa/aaaBridge

This key will be described in the Section 14.9, “Populating AAA using external data” section.

/confdConfig/aaa/externalAuthentication

ConfD can authenticate users using an external executable. This is further described later in the Section 14.4.4, “External authentication” section.

/confdConfig/aaa/authenticationCallback/enabled

If this is set to "true", ConfD will, as the last step of every authentication attempt, invoke an application callback. The callback can reject an otherwise successful authentication. See the section called “AUTHENTICATION CALLBACK” in the confd_lib_dp(3) manual page for the details about this.

/confdConfig/aaa/authorization/callback/enabled

If this is set to "true", ConfD will invoke application callbacks for authorization. The callbacks can partially or completely replace the logic described in Section 14.6, “Authorization”. See the section called “AUTHORIZATION CALLBACKS” in the confd_lib_dp(3) manual page for the details about this.

14.4. Authentication

Depending on northbound management protocol, when a user session is created in ConfD, it may or may not be authenticated. If the session is not yet authenticated, ConfD's AAA subsystem is used to perform authentication and authorization, as described below. If the session already has been authenticated, ConfD's AAA assigns groups to the user as described in Section 14.5, “Group Membership”, and performs authorization, as described in Section 14.6, “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 function maapi_authenticate() is used.

The different authentication mechanisms that may be used in these cases are described below. Regardless of which mechanism that is used, ConfD can optionally invoke an application callback as the last step of the authentication process, see the section called “AUTHENTICATION CALLBACK” in confd_lib_dp(3). The callback is not used for the actual authentication, but it can reject an otherwise successful authentication.

ConfD'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 Section 15.3, “NETCONF Transport Protocols”.

  • When NETCONF uses TCP, as described in Section 15.3, “NETCONF Transport Protocols”, e.g. through the command netconf-console.

  • When the CLI uses an external SSH daemon, such as OpenSSH, or a telnet daemon.

    In this case, the CLI session is initiated through the command confd_cli . An important special case here is when a user has logged in to the host and invokes the command confd_cli from the shell.

  • When SNMP is used. SNMP has its own authentication mechanisms. See Section 17.5.2, “USM and VACM and ConfD AAA” .

  • When the function maapi_start_user_session() is used without a preceding call of maapi_authenticate().

14.4.1. Public Key Login

When a user logs in over NETCONF or the CLI using the built-in SSH server, with 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 /confdConfig/aaa/sshPubkeyAuthentication and /confdConfig/aaa/localAuthentication/enabled .

  1. If sshPubkeyAuthentication 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 sshPubkeyAuthentication is set to system, localAuthentication 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 sshPubkeyAuthentication 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 Section 14.5, “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 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 and RSA 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

14.4.2. 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 function maapi_authenticate() is used.

In this case, ConfD will by default try local authentication, PAM, and external authentication, in that order, as described below. It is possible to change the order in which these are tried, by modifying the confd.conf . parameter /confdConfig/aaa/authOrder . See confd.conf(5) 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 ???? for details.

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

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

14.4.3. PAM

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

If we use PAM, we do not have to have any users or any groups configured in the ConfD 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 confd.conf by adding:

    <pam>
      <enabled>true</enabled>
      <service>common-auth</service>
    </pam>
            

    to the aaa section in confd.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 to ConfD than to the host itself.

  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-auth" will typically require root since the pam libraries then read /etc/shadow. If we don't want to run ConfD as root, the solution here is to change owner of a helper program called $CONFD_DIR/lib/confd/lib/core/pam/priv/epam and also set the setuid bit.

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

PAM is the recommended way to authenticate ConfD users.

As an example, say that we have 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 ConfD 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 confd[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 confd[14658]: audit user: test/5 assigned
    to groups: operator,admin
<INFO> 28-Jan-2009::16:05:57.655 buzz confd[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, setup 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.

14.4.4. 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 ConfD authentication is to use "external authentication".

If this feature is configured, ConfD will invoke the executable configured in /confdConfig/aaa/externalAuthentication/executable in confd.conf , and pass the username and the clear text password on stdin using the string notation: "[user;password;]\n".

For example if user "bob" attempts to login over SSH using the password "secret", and external authentication is enabled, ConfD 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 the user is member of, and additional information as described below. Again, assuming that Bob's password indeed was "secret", and that Bob is member of the "admin" and the "lamers" groups, the program should write "accept admin lamers $uid $gid $supplementary_gids $HOME\n" on its standard output and then exit.

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 ConfD should use as default when executing commands for this user.

  • $gid is the UNIX integer group id ConfD should use as 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 which should be used as HOME for this user when ConfD executes commands on behalf of this user.

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. ConfD 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 ConfD according to the setting of /confdConfig/aaa/expirationWarning in confd.conf .

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", ConfD will try subsequent mechanisms configured for /confdConfig/aaa/authOrder in confd.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 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 ConfD when the authorization rules are checked.

14.5. 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 ConfD 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 confd_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 (/confdConfig/aaa/defaultGroup in confd.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 ConfD 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 gids in the groups where the user is a member.

14.6. 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, but if needed we can also register application callbacks to partially or completely replace this logic, see the section called “AUTHORIZATION CALLBACKS” in confd_lib_dp(3).

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.

14.6.1. 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 ConfD 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 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 which consists of just "*" matches any command.

It is important to understand that a command rule for the CLI applies to the string as entered by the user. The command rules are not aware of the data model. Thus it is not possible to have a rule like:

<cmdrule>
  <name>delete-eth0</name>
  <context>cli</context>
  <command>delete interfaces interface eth0</command>
  <access-operations>exec</access-operations>
  <action>deny</action>
</cmdrule>

to protect a specific interface from removal in The Juniper CLI. The user can enter:

joe@host% edit interfaces
joe@host% delete interface eth0

making the command rule above moot.

In the Cisco like CLIs it makes more sense to use command rules to protect data. This is due to the command oriented character of the Cisco CLIs.

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.

Another thing that is important for command rule processing of CLI commands is the mode. If we enable the feature /confdConfig/cli/modeInfoInAAA, the command rule matching will match on a string where the CLI mode is prepended. This makes command rule processing more useful in the Cisco style CLIs than in the Juniper style CLI.

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 ConfD 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 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 /confdConfig/logs/developerLogLevel entry in confd.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.

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

name is the name of the rule. The rules are checked in order, with the ordering given by the 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 the rule applies to. The special value * matches all modules.

rpc-name / notification-name / path

This is a choice between three possible leafs that are used for matching:

rpc-name

The name of a 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 are not containing any keys. For example /interfaces/interface/mtu .

  2. Instantiated key: as in /interfaces/interface[name="eth0"]/mask matches the mask element only for the interface name "eth0". 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 /hosts/host[name="venus"]/servers/server/ip matches the "ip" element for all servers, but only for the host named "venus".

  3. Wild card at end as in: /interfaces/interface/* does not match /interfaces/interface but rather all children of that path.

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 ConfD 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 presence of the NACM extensions:

  • If the nacm:default-deny-all extension is specified for the data model node, 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, 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 the access.

When ConfD 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 ConfD 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 an 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:

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:

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

The idea being 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.

14.6.3. Authorization Examples

Assume that we have two groups, admin and oper. We want admin to be able to see and and edit the XML tree rooted at /aaa, but we do not want users that 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 ConfD tries to perform a netconf operation, which is the operation edit-config, and the user which runs the command is 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>
    <module-name>tailf-aaa</module-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.

ConfD 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>
    <module-name>tailf-aaa</module-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.

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>
  

Debugging the AAA rules can be hard. The best way to debug rules that behave unexpectedly is to add the log-if-permit leaf to some or all of the rules that have action permit. Whenever such a rule triggers a permit action, an entry is written to the developer log.

Finally it is worth mentioning that when a user session is initially created it will gather the authorization rules that are relevant for that user session and keep these rules for the life of the user session. Thus when we update the AAA rules in e.g. the CLI the update will not apply to the current session - only to future user sessions.

14.7. The AAA cache

ConfD'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.

14.8. Populating AAA using CDB

In order to start ConfD, 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 AAA data can either be stored in CDB or in an external daemon as described in the chapter Chapter 7, The external database API. The only new problem when we use CDB to store the AAA data is to initialize the AAA database. This can be done as described in the chapter Chapter 5, CDB - The ConfD XML Database, from an XML document containing real data.

ConfD ships with a decent initialization document for the AAA database. The file is called aaa_init.xml and is by default copied to the CDB directory by the ConfD install scripts. The file defines two users, admin and oper with passwords set to admin and oper respectively.

Normally the AAA data will be stored as configuration in CDB. This allows for changes to be made through ConfD'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 ConfD'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 ???? manual page, or by triggering a subscription notification by using the "subscription lock" when updating the CDB operational data store, see ????.

14.9. Populating AAA using external data

Note

The confd_aaa_bridge program described here is deprecated. It does not support the NACM data model. It may still be useful to study this as an example of an external data provider for AAA data, however the implementation follows the same principles as for other external data providers.

An alternative to storing the AAA data in CDB is to store it outside of ConfD. ConfD comes with an example implementation of such a program. It is called confd_aaa_bridge and is fully described in the man page confd_aaa_bridge(1).

The procedure here is precisely the same as with any other data model - with the exception that ConfD itself is a user of this data and reads it. Thus if using CDB to store the AAA data is not an option, the API for external databases must be used to populate the AAA tree.

The YANG model which describes this is the same tailf-aaa.yang but annotated with a callpoint. It is shipped together with the example implementation called confd_aaa_bridge. When we compile the tailf-aaa.yang with the callpoint for external data we name the resulting file aaa_bridge.fxs.

Note

The name of the fxs file is not significant, the aaa_bridge.fxs name is used here only to distinguish it from the aaa_cdb.fxs name that has been used for the CDB version of legacy tailf-aaa. We can equally well use the more "natural" name tailf-aaa.fxs in both cases.

Note

We must additionally annotate the ietf-netconf-acm.yang module with a callpoint and compile it, if the group assignment and authorization data is to be provided by an external data provider. This annotation must be done in addition to the annotation done by the ietf-netconf-acm-ann.yang module included in the ConfD release.

The example program confd_aaa_bridge.c which is delivered as source code in the ConfD release contains an example implementation of external storage of the AAA data in an ad hoc .ini style file. (This is only of interest for users that do not use CDB to store any data at all.) See the UNIX man page confd_aaa_bridge(1).

The confd_aaa_bridge program implements a configuration data provider, and thus changes to the AAA data can be made through ConfD's configuration management just as when the data is stored as configuration in CDB. And similar to the CDB case, if changing the AAA data via ConfD's configuration management is not possible or desirable, it is alternatively possible to let the AAA data be provided by an operational (i.e. read-only) external data provider. In either case, when we use an external data provider for AAA, the AAA cache must always be updated explicitly, e.g. by using the maapi_aaa_reload() function, see the confd_lib_maapi(3) manual page. For a configuration data provider, the confd_aaa_reload() function may be more convenient, see the confd_lib_dp(3) manual page - this function is used by confd_aaa_bridge.

14.10. 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 which maps our AAA data to the data which 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.