Description

Customers want TACACS Plus server to authenticate Juniper devices using an OpenLDAP backend server and authorize permissions based on group membership retrieved from the OpenLDAP server

Solution

Because tac_plus-ng introduced some significant enhanced Authentication Backends below, we select it as TACACS Plus server here.

 

Major enhanced Authentication Backends of tac_plus-ng:

(1) Supports flexible external backends for user profiles, including LDAP (with Active Directory support), RADIUS, PAM, and custom scripts (e.g., PERL or C).

This allows integration with modern identity management systems like FreeIPA or Microsoft Active Directory.

Includes the MAVIS (Modular Authentication, Verification, and Identity Subsystem) backend for robust authentication and authorization, enabling seamless integration with various identity sources.

(2) Supports non-standard SSH Public Key Authentication and experimental TLS features, which could potentially lead to RFC standardization if proven stable.

 

Download and installation:

1) Please check KB99448 [juniper.net] for installation of tac_plus-ng

2) Please check Ubuntu official website below for installation of OpenLDAP:

    https://documentation.ubuntu.com/server/how-to/openldap/install-openldap/index.html

 

Configure tac_plus-ng, OpenLDAP and Juniper devices:

1) We create two statically configured users below in tac_plus-ng conf:

    jtac1 and jtac2 who belong to tac_plus-ng groups net_admin_users and net_operator respectively.

 

2) Create 4 users below in OpenLDAP servers.

    user1, user2, user3 and user4 who belong to groups(objectclass GroupOfNames) net_admin_users, net_operator, net_Read_Only and net_ts respectively.

 

3) Create 4 user templates on Juniper devices:

   

configure
set system login user net_admin_users class super-user
set system login user operator_user class operator
set system login user RO_user class read-only
set system login user ts_user class class_ts_user
set system login class class_ts_user login-tip
set system authentication-order tacplus
set system authentication-order password
set system tacplus-server <ip_of_tacacs_server> secret lab123
commit and-quit

 

4) The logic of tac_plus-ng config is as below:

    Create groups, profiles which include permissions and then use ruleset to map groups to profiles.

    tac_plus-ng can use a MAVIS Perl script to retrieve group memberships from a backend OpenLDAP server, match users to the appropriate profiles based on the ruleset defined in the tac_plus-ng configuration, and authorize user permissions accordingly.

 

5) Login Ubuntu which has OpenLDAP installed, add users, groups and OUs to OpenLDAP.

   let's assume domain name is jtac.net, all users and groups belong to OU users and groups respectively.

    password of all users are test123, admin password of OpenLDAP is test123 as well.

   Run cmds below undershell of OpenLDAP server which will create corresponding OUs, Groups and Users.

 

HASHED_PASS=$(slappasswd -h {SSHA} -s test123)

echo "dn: uid=user1,ou=users,dc=jtac,dc=net" > users.ldif
echo "objectClass: inetOrgPerson" >> users.ldif
echo "uid: user1" >> users.ldif
echo "cn: user1" >> users.ldif
echo "sn: user1" >> users.ldif
echo "userPassword: $HASHED_PASS" >> users.ldif
echo "">> users.ldif
echo "dn: uid=user2,ou=users,dc=jtac,dc=net" >> users.ldif
echo "objectClass: inetOrgPerson" >> users.ldif
echo "uid: user2" >> users.ldif
echo "cn: user2" >> users.ldif
echo "sn: user2" >> users.ldif
echo "userPassword: $HASHED_PASS" >> users.ldif
echo "" >> users.ldif
echo "dn: uid=user3,ou=users,dc=jtac,dc=net" >> users.ldif
echo "objectClass: inetOrgPerson" >> users.ldif
echo "uid: user3" >> users.ldif
echo "cn: user3" >> users.ldif
echo "sn: user3" >> users.ldif
echo "userPassword: $HASHED_PASS" >> users.ldif
echo "" >> users.ldif
echo "dn: uid=user4,ou=users,dc=jtac,dc=net" >> users.ldif
echo "objectClass: inetOrgPerson" >> users.ldif
echo "uid: user4" >> users.ldif
echo "cn: user4" >> users.ldif
echo "sn: user4" >> users.ldif
echo "userPassword: $HASHED_PASS" >> users.ldif

echo "dn: ou=users,dc=jtac,dc=net" > ous.ldif
echo "objectClass: organizationalUnit" >> ous.ldif
echo "ou: users" >> ous.ldif
echo "" >> ous.ldif
echo "dn: ou=groups,dc=jtac,dc=net" >> ous.ldif
echo "objectClass: organizationalUnit" >> ous.ldif
echo "ou: groups" >> ous.ldif

echo "dn: cn=net_admin_users,ou=groups,dc=jtac,dc=net" > groups.ldif
echo "objectClass: groupOfNames" >> groups.ldif
echo "cn: net_admin_users" >> groups.ldif
echo "member: uid=user1,ou=users,dc=jtac,dc=net" >> groups.ldif
echo "" >> groups.ldif
echo "dn: cn=net_operator,ou=groups,dc=jtac,dc=net" >> groups.ldif
echo "objectClass: groupOfNames" >> groups.ldif
echo "cn: net_operator" >> groups.ldif
echo "member: uid=user2,ou=users,dc=jtac,dc=net" >> groups.ldif
echo "" >> groups.ldif
echo "dn: cn=net_Read_Only,ou=groups,dc=jtac,dc=net" >> groups.ldif
echo "objectClass: groupOfNames" >> groups.ldif
echo "cn: net_Read_Only" >> groups.ldif
echo "member: uid=user3,ou=users,dc=jtac,dc=net" >> groups.ldif
echo "" >> groups.ldif
echo "dn: cn=net_ts,ou=groups,dc=jtac,dc=net" >> groups.ldif
echo "objectClass: groupOfNames" >> groups.ldif
echo "cn: net_ts" >> groups.ldif
echo "member: uid=user4,ou=users,dc=jtac,dc=net" >> groups.ldif

ldapadd -x -D "cn=admin,dc=jtac,dc=net" -w test123 -f ous.ldif
ldapadd -x -D "cn=admin,dc=jtac,dc=net" -w test123 -f groups.ldif
ldapadd -x -D "cn=admin,dc=jtac,dc=net" -w test123 -f users.ldif

 

6)   Start tac_plus-ng with example config below on tac_plus-ng server:

     lab@vm:$  tac_plus-ng -d 50 -f <tac_plus-ng_conf>

 ### start of tac_plus-ng conf ###

#!/usr/local/sbin/tac_plus-ng
id = spawnd {
    listen = { address = 0.0.0.0 port = 49 }
    # Optional: IPv6 support
    # listen = { address = :: port = 49 }
    spawn = { instances min = 1 instances max = 10 }
    background = yes
}

id = tac_plus-ng {
    log accesslog { destination = /var/log/tac_plus/access/%Y/%m/access-%m-%d-%Y.txt }
    log accountinglog { destination = /var/log/tac_plus/accounting/%Y/%m/accounting-%m-%d-%Y.txt }
    log authenticationlog { destination = /var/log/tac_plus/authentication/%Y/%m/authentication-%m-%d-%Y.txt }

    access log = accesslog
    accounting log = accountinglog
    authentication log = authenticationlog

    mavis module = external {
        # Set environment variables for LDAP connection
        setenv LDAP_SERVER_TYPE = "openldap"
        # using localhost if openldap server and tac_plus-ng are installed on same server.
        setenv LDAP_HOSTS = "ldap://localhost:389"
        setenv LDAP_BASE = "dc=jtac,dc=net"
        setenv LDAP_BINDDN = "cn=admin,dc=jtac,dc=net"
        setenv LDAP_BINDPW = "test123"
        setenv REQUIRE_TACACS_GROUP_PREFIX = 0
        setenv LDAP_FILTER = "(uid=%s)"
        setenv LDAP_GROUP_FILTER = "(|(&(objectClass=groupOfNames)(member=%s))(&(objectClass=posixGroup)(memberUid=%u)))"

        # Optional: setenv REQUIRE_TACACS_GROUP_PREFIX = 0
        # Do NOT set USE_TLS unless you are sure; see notes below

        exec = /usr/local/lib/mavis/mavis_tacplus-ng_ldap.pl
    }

    # Use mavis for all authentication backends
    login backend = mavis
    user backend = mavis
    pap backend = mavis

    # Define access policies
    host = Juniper {
        address = 0.0.0.0/0
        key = "lab123"
    }

    group net_admin_users
    group net_operator
    group net_Read_Only
    group net_ts
    
    user jtac1 {
        password login = clear lab123
        member = net_admin_users
    }

    user jtac2 {
        password login = clear lab123
        member = net_operator
    }

    profile net_admin_users_profile {
        script {
            if (service == junos-exec) {
                set local-user-name = admin_user
                permit
            }
            deny
        }
    }

    profile net_operator_users_profile {
        script {
            if (service == junos-exec) {
                set local-user-name = operator_user
                permit
            }
            deny
        }
    }

    profile net_RO_users_profile {
        script {
            if (service == junos-exec) {
                set local-user-name = RO_user
                set deny-commands = "(^configure .*)|(^scp .*)|(^op .*)|(^telnet .*)|(^request .*)"
                permit
            }
            deny
        }
    }

    profile net_ts_users_profile {
        script {
            if (service == junos-exec) {
                set local-user-name = ts_user
                set allow-commands = "(^ping .*)|(^traceroute .*)"
                permit
            }
            deny
        }
    }

    ruleset {
        rule Juniper_rule {
            enabled = yes
            script {
                if (group == net_admin_users) {
                    profile = net_admin_users_profile
                    permit
                }
                if (group == net_operator) {
                    profile = net_operator_users_profile
                    permit
                }
                if (group == net_Read_Only) {
                    profile = net_RO_users_profile
                    permit
                }
                if (group == net_ts) {
                    profile = net_ts_users_profile
                    permit
                }
                deny
            }
        }
    }

}

Modification History

2025-06-02 : Article Created