Skip to content

Commit

Permalink
[JIRA SONIC-10898] RADIUS Management User Authentication Feature (1)
Browse files Browse the repository at this point in the history
* First Cut for RADIUS Management User Authentication Feature.
* PAP/CHAP Authentication.
* NSS Module.
* Click Cli

Change-Id: Ie079bb7a19f8626ed0470abf3379084b6f3342e2
  • Loading branch information
a-barboza committed Oct 22, 2019
1 parent fa5f304 commit 8ea8340
Show file tree
Hide file tree
Showing 37 changed files with 1,965 additions and 7 deletions.
10 changes: 10 additions & 0 deletions files/build_templates/sonic_debian_extension.j2
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libnss-tacplus_*.deb || \
sudo LANG=C chroot $FILESYSTEM_ROOT pam-auth-update --remove tacplus
sudo sed -i -e '/^passwd/s/ tacplus//' $FILESYSTEM_ROOT/etc/nsswitch.conf

# Install pam-radius-auth and nss-radius
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libpam-radius-auth_*.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/libnss-radius_*.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
# Disable radius by default
# radius does not have any profiles
#sudo LANG=C chroot $FILESYSTEM_ROOT pam-auth-update --remove radius tacplus
sudo sed -i -e '/^passwd/s/ radius//' $FILESYSTEM_ROOT/etc/nsswitch.conf

#Install the HWDIAG package
sudo dpkg --root=$FILESYSTEM_ROOT -i $debs_path/hwdiag*.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
Expand Down
36 changes: 36 additions & 0 deletions files/image_config/hostcfgd/common-auth-sonic.j2
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,42 @@ auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not
{% endfor %}
auth [success=1 default=ignore] pam_unix.so nullok try_first_pass

{% elif auth['login'] == 'local,radius' %}
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass
# For the RADIUS servers, on success jump to the cacheing the MPL(Privilege)
{% for server in servers %}
auth [success={{ (servers | count) - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}:{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }} try_first_pass
{% endfor %}
auth requisite pam_deny.so
# Cache MPL(Privilege)
auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius

{% elif auth['login'] == 'radius,local' %}
# root user can only be authenticated locally. Jump to local.
auth [success={{ (servers | count) }} default=ignore] pam_succeed_if.so user = root
# For the RADIUS servers, on success jump to the cache the MPL(Privilege)
{% for server in servers %}
auth [success={{ (servers | count) + 1 - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}:{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }} try_first_pass
{% endfor %}
# Local
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass
auth requisite pam_deny.so
# Cache MPL(Privilege)
auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius

{% elif auth['login'] == 'radius' %}
# root user can only be authenticated locally. Jump to local.
auth [success={{ (servers | count) + 1 }} default=ignore] pam_succeed_if.so user = root
# For the RADIUS servers, on success jump to the cache the MPL(Privilege)
{% for server in servers %}
auth [success={{ (servers | count) - loop.index0 }} new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_radius_auth.so conf=/etc/pam_radius_auth.d/{{ server.ip }}:{{ server.auth_port }}.conf privilege_level protocol={{ server.auth_type }} retry={{ server.retransmit }} try_first_pass
{% endfor %}
auth requisite pam_deny.so
# Cache MPL(Privilege)
auth [success=1 default=ignore] pam_exec.so /usr/sbin/cache_radius
# Local
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die maxtries=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass

{% else %}
auth [success=1 default=ignore] pam_unix.so nullok try_first_pass

Expand Down
113 changes: 106 additions & 7 deletions files/image_config/hostcfgd/hostcfgd
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,24 @@ PAM_AUTH_CONF = "/etc/pam.d/common-auth-sonic"
PAM_AUTH_CONF_TEMPLATE = "/usr/share/sonic/templates/common-auth-sonic.j2"
NSS_TACPLUS_CONF = "/etc/tacplus_nss.conf"
NSS_TACPLUS_CONF_TEMPLATE = "/usr/share/sonic/templates/tacplus_nss.conf.j2"
NSS_RADIUS_CONF = "/etc/radius_nss.conf"
NSS_RADIUS_CONF_TEMPLATE = "/usr/share/sonic/templates/radius_nss.conf.j2"
PAM_RADIUS_AUTH_CONF_TEMPLATE = "/usr/share/sonic/templates/pam_radius_auth.conf.j2"
NSS_CONF = "/etc/nsswitch.conf"

# TACACS+
TACPLUS_SERVER_PASSKEY_DEFAULT = ""
TACPLUS_SERVER_TIMEOUT_DEFAULT = "5"
TACPLUS_SERVER_AUTH_TYPE_DEFAULT = "pap"

# RADIUS
RADIUS_SERVER_AUTH_PORT_DEFAULT = "1812"
RADIUS_SERVER_PASSKEY_DEFAULT = ""
RADIUS_SERVER_RETRANSMIT_DEFAULT = "3"
RADIUS_SERVER_TIMEOUT_DEFAULT = "5"
RADIUS_SERVER_AUTH_TYPE_DEFAULT = "pap"
RADIUS_PAM_AUTH_CONF_DIR = "/etc/pam_radius_auth.d/"


def is_valid_hostname(name):
if hostname[-1] == ".":
Expand Down Expand Up @@ -60,19 +71,32 @@ class AaaCfg(object):
'timeout': TACPLUS_SERVER_TIMEOUT_DEFAULT,
'passkey': TACPLUS_SERVER_PASSKEY_DEFAULT
}
self.auth = {}
self.tacplus_global = {}
self.tacplus_servers = {}
self.radius_global_default = {
'auth_port': RADIUS_SERVER_AUTH_PORT_DEFAULT,
'auth_type': RADIUS_SERVER_AUTH_TYPE_DEFAULT,
'retransmit': RADIUS_SERVER_RETRANSMIT_DEFAULT,
'timeout': RADIUS_SERVER_TIMEOUT_DEFAULT,
'passkey': RADIUS_SERVER_PASSKEY_DEFAULT
}
self.radius_global = {}
self.radius_servers = {}
self.auth = {}
self.debug = False

# Load conf from ConfigDb
def load(self, aaa_conf, tac_global_conf, tacplus_conf):
def load(self, aaa_conf, tac_global_conf, tacplus_conf, rad_global_conf, radius_conf):
for row in aaa_conf:
self.aaa_update(row, aaa_conf[row], modify_conf=False)
for row in tac_global_conf:
self.tacacs_global_update(row, tac_global_conf[row], modify_conf=False)
for row in tacplus_conf:
self.tacacs_server_update(row, tacplus_conf[row], modify_conf=False)
for row in rad_global_conf:
self.radius_global_update(row, rad_global_conf[row], modify_conf=False)
for row in radius_conf:
self.radius_server_update(row, radius_conf[row], modify_conf=False)
self.modify_conf_file()

def aaa_update(self, key, data, modify_conf=True):
Expand Down Expand Up @@ -101,6 +125,22 @@ class AaaCfg(object):
if modify_conf:
self.modify_conf_file()

def radius_global_update(self, key, data, modify_conf=True):
if key == 'global':
self.radius_global = data
if modify_conf:
self.modify_conf_file()

def radius_server_update(self, key, data, modify_conf=True):
if data == {}:
if key in self.radius_servers:
del self.radius_servers[key]
else:
self.radius_servers[key] = data

if modify_conf:
self.modify_conf_file()

def modify_conf_file(self):
auth = self.auth_default.copy()
auth.update(self.auth)
Expand All @@ -116,11 +156,27 @@ class AaaCfg(object):
servers_conf.append(server)
servers_conf = sorted(servers_conf, key=lambda t: t['priority'], reverse=True)

radius_global = self.radius_global_default.copy()
radius_global.update(self.radius_global)

radsrvs_conf = []
if self.radius_servers:
for addr in self.radius_servers:
server = radius_global.copy()
server['ip'] = addr
server.update(self.radius_servers[addr])
radsrvs_conf.append(server)
radsrvs_conf = sorted(radsrvs_conf, key=lambda t: t['priority'], reverse=True)

template_file = os.path.abspath(PAM_AUTH_CONF_TEMPLATE)
env = jinja2.Environment(loader=jinja2.FileSystemLoader('/'), trim_blocks=True)
env.filters['sub'] = sub
template = env.get_template(template_file)
pam_conf = template.render(auth=auth, servers=servers_conf)
if 'radius' in auth['login']:
pam_conf = template.render(auth=auth, servers=radsrvs_conf)
else:
pam_conf = template.render(auth=auth, servers=servers_conf)

with open(PAM_AUTH_CONF, 'w') as f:
f.write(pam_conf)

Expand All @@ -132,13 +188,16 @@ class AaaCfg(object):
os.system("sed -i -e '/^@include/s/common-auth-sonic$/common-auth/' /etc/pam.d/sshd")
os.system("sed -i -e '/^@include/s/common-auth-sonic$/common-auth/' /etc/pam.d/login")

# Add tacplus in nsswitch.conf if TACACS+ enable
# Add tacplus/radius in nsswitch.conf if TACACS+/RADIUS enable
if 'tacacs+' in auth['login']:
if os.path.isfile(NSS_CONF):
os.system("sed -i -e '/tacplus/b' -e '/^passwd/s/compat/tacplus &/' /etc/nsswitch.conf")
os.system("sed -i -e '/^passwd/s/ radius//' -e '/tacplus/b' -e '/^passwd/s/compat/tacplus &/' /etc/nsswitch.conf")
elif 'radius' in auth['login']:
if os.path.isfile(NSS_CONF):
os.system("sed -i -e '/^passwd/s/tacplus //' -e '/radius/b' -e '/^passwd/s/compat/& radius/' /etc/nsswitch.conf")
else:
if os.path.isfile(NSS_CONF):
os.system("sed -i -e '/^passwd/s/tacplus //' /etc/nsswitch.conf")
os.system("sed -i -e '/^passwd/s/tacplus //' -e '/^passwd/s/ radius//' /etc/nsswitch.conf")

# Set tacacs+ server in nss-tacplus conf
template_file = os.path.abspath(NSS_TACPLUS_CONF_TEMPLATE)
Expand All @@ -147,6 +206,28 @@ class AaaCfg(object):
with open(NSS_TACPLUS_CONF, 'w') as f:
f.write(nss_tacplus_conf)

# Set debug in nss-radius conf
template_file = os.path.abspath(NSS_RADIUS_CONF_TEMPLATE)
template = env.get_template(template_file)
nss_radius_conf = template.render(debug=self.debug, servers=radsrvs_conf)
with open(NSS_RADIUS_CONF, 'w') as f:
f.write(nss_radius_conf)

# Create the per server pam_radius_auth.conf
if self.radius_servers:
for addr in self.radius_servers:
srv = radius_global.copy()
srv['ip'] = addr
srv.update(self.radius_servers[addr])
pam_radius_auth_file = RADIUS_PAM_AUTH_CONF_DIR + addr + ":" + srv['auth_port'] + ".conf"
template_file = os.path.abspath(PAM_RADIUS_AUTH_CONF_TEMPLATE)
template = env.get_template(template_file)
pam_radius_auth_conf = template.render(server=srv)

with open(pam_radius_auth_file, 'w') as f:
f.write(pam_radius_auth_conf)



class HostConfigDaemon:
def __init__(self):
Expand All @@ -156,8 +237,10 @@ class HostConfigDaemon:
aaa = self.config_db.get_table('AAA')
tacacs_global = self.config_db.get_table('TACPLUS')
tacacs_server = self.config_db.get_table('TACPLUS_SERVER')
radius_global = self.config_db.get_table('RADIUS')
radius_server = self.config_db.get_table('RADIUS_SERVER')
self.aaacfg = AaaCfg()
self.aaacfg.load(aaa, tacacs_global, tacacs_server)
self.aaacfg.load(aaa, tacacs_global, tacacs_server, radius_global, radius_server)
self.hostname_cache=""

def aaa_handler(self, key, data):
Expand All @@ -177,6 +260,20 @@ class HostConfigDaemon:
log_data['passkey'] = obfuscate(log_data['passkey'])
syslog.syslog(syslog.LOG_INFO, 'value of {} changed to {}'.format(key, log_data))

def radius_server_handler(self, key, data):
self.aaacfg.radius_server_update(key, data)
log_data = copy.deepcopy(data)
if log_data.has_key('passkey'):
log_data['passkey'] = obfuscate(log_data['passkey'])
syslog.syslog(syslog.LOG_INFO, 'value of {} changed to {}'.format(key, log_data))

def radius_global_handler(self, key, data):
self.aaacfg.radius_global_update(key, data)
log_data = copy.deepcopy(data)
if log_data.has_key('passkey'):
log_data['passkey'] = obfuscate(log_data['passkey'])
syslog.syslog(syslog.LOG_INFO, 'value of {} changed to {}'.format(key, log_data))

def hostname_handler(self, key, data):
if key != "localhost":
return
Expand Down Expand Up @@ -219,6 +316,8 @@ class HostConfigDaemon:
self.config_db.subscribe('AAA', lambda table, key, data: self.aaa_handler(key, data))
self.config_db.subscribe('TACPLUS_SERVER', lambda table, key, data: self.tacacs_server_handler(key, data))
self.config_db.subscribe('TACPLUS', lambda table, key, data: self.tacacs_global_handler(key, data))
self.config_db.subscribe('RADIUS_SERVER', lambda table, key, data: self.radius_server_handler(key, data))
self.config_db.subscribe('RADIUS', lambda table, key, data: self.radius_global_handler(key, data))
self.config_db.subscribe('DEVICE_METADATA', lambda table, key, data: self.hostname_handler(key, data))
self.config_db.listen()

Expand Down
3 changes: 3 additions & 0 deletions files/image_config/hostcfgd/pam_radius_auth.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# server[:port] shared_secret timeout(s) source_ip vrf
{{ server.ip }}:{{ server.auth_port }} {{ server.passkey }} {{ server.timeout }} {% if server.src_ip %} {{ server.src_ip }} {% endif %} {% if server.vrf %} {% if not server.src_ip %} - {% endif %} {{ server.vrf }}{% endif %}

37 changes: 37 additions & 0 deletions files/image_config/hostcfgd/radius_nss.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# RADIUS NSS Configuration File
#
# Debug: on|off|trace
# Default: off
#
# debug=on
{% if debug %}
debug=on
{% endif %}

#
# User Privilege:
# Default:
# user_priv=15;pw_info=remote_user_su;uid=1000;gid=1000;group=sudo,docker;dir=/home/admin;shell=/bin/bash
# user_priv=1;pw_info=remote_user;uid=65534;gid=65534;group=users;dir=/var/tmp;shell=/bin/bash

# Eg:
# First need to create netops, operator using:
# useradd netops -G users -u 2007 -g 100 -c "netops" -m -s /bin/bash
# useradd operator -G users -u 2001 -g 100 -c "operator" -m -s /bin/rbash
#
# Then uncomment the lines below.
#
# user_priv=15;pw_info=remote_user_su;uid=1000;gid=1000;group=sudo,docker;dir=/home/admin;shell=/bin/bash
# user_priv=7;pw_info=netops;uid=2007;gid=100;group=users;dir=/home/netops;shell=/bin/bash
# user_priv=1;pw_info=operator;uid=2001;gid=100;group=users;dir=/home/operator;shell=/bin/rbash

# many_to_one:
# y: Map TACACS+ users to one local user per privilege.
# n: Create local user account on first successful authentication.
# a: Anonymous: Return least privileged(user_priv=1) user info for unknown.
# Default: n
#

# Eg:
# many_to_one=y
#
24 changes: 24 additions & 0 deletions rules/radius.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# libpam-radius-auth packages

PAM_RADIUS_VERSION = 1.4.1-1

export PAM_RADIUS_VERSION

LIBPAM_RADIUS = libpam-radius-auth_$(PAM_RADIUS_VERSION)_amd64.deb
$(LIBPAM_RADIUS)_SRC_PATH = $(SRC_PATH)/radius/pam
SONIC_MAKE_DEBS += $(LIBPAM_RADIUS)

SONIC_STRETCH_DEBS += $(LIBPAM_RADIUS)

# libnss-radius packages

NSS_RADIUS_VERSION = 1.0.1-1

export NSS_RADIUS_VERSION

LIBNSS_RADIUS = libnss-radius_$(NSS_RADIUS_VERSION)_amd64.deb
$(LIBNSS_RADIUS)_SRC_PATH = $(SRC_PATH)/radius/nss
SONIC_MAKE_DEBS += $(LIBNSS_RADIUS)

SONIC_STRETCH_DEBS += $(LIBNSS_RADIUS)

2 changes: 2 additions & 0 deletions slave.mk
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,8 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \
$(IFUPDOWN2) \
$(HWDIAG) \
$(NTP) \
$(LIBPAM_RADIUS) \
$(LIBNSS_RADIUS) \
$(LIBPAM_TACPLUS) \
$(LIBNSS_TACPLUS)) \
$$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \
Expand Down
23 changes: 23 additions & 0 deletions src/radius/nss/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
.ONESHELL:
SHELL = /bin/bash
.SHELLFLAGS += -e

MAIN_TARGET = libnss-radius_$(NSS_RADIUS_VERSION)_amd64.deb

$(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% :
pushd ./libnss-radius

make clean
-rm -rf debian
-rm -rf patches
cp -r ../debian .
cp -r ../patches .

# Apply patch (if any)

dpkg-buildpackage -rfakeroot -b -us -uc
popd

mv $(DERIVED_TARGETS) $* $(DEST)/

$(addprefix $(DEST)/, $(DERIVED_TARGETS)): $(DEST)/% : $(DEST)/$(MAIN_TARGET)
8 changes: 8 additions & 0 deletions src/radius/nss/debian/README.Debian
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
libnss-radius for Debian

Please edit this to provide information specific to
this libnss-radius Debian package.

(Automatically generated by debmake Version 4.2.2)

-- Arun Barboza <[email protected]> Tue, 24 Sep 2019 00:20:55 +0000
6 changes: 6 additions & 0 deletions src/radius/nss/debian/changelog
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
libnss-radius (1.0.1-1) unstable; urgency=low

* Initial release. NSS lookups for RADIUS users with cached Management
Privilege Level (MPL) attribute.

-- Arun Barboza <[email protected]> Tue, 24 Sep 2019 00:20:55 +0000
1 change: 1 addition & 0 deletions src/radius/nss/debian/compat
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9
17 changes: 17 additions & 0 deletions src/radius/nss/debian/control
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
Source: libnss-radius
Section: libs
Priority: optional
Maintainer: Arun Barboza <[email protected]>
Build-Depends: debhelper (>=9)
Standards-Version: 3.9.6
Homepage: http://www.broadcom.com

Package: libnss-radius
Section: libs
Architecture: any
Multi-Arch: same
Pre-Depends: ${misc:Pre-Depends}
Depends: ${misc:Depends}, ${shlibs:Depends}
Description: NSS module for RADIUS authentication absent local account.
NSS lookups for RADIUS authenticated users using the Management Privilege
Level (MPL) cached attribute.
Loading

0 comments on commit 8ea8340

Please sign in to comment.