Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions files/build_templates/sonic_debian_extension.j2
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ sudo cp $IMAGE_CONFIGS/interfaces/*.j2 $FILESYSTEM_ROOT/usr/share/sonic/template
# Copy initial interfaces configuration file, will be overwritten on first boot
sudo cp $IMAGE_CONFIGS/interfaces/init_interfaces $FILESYSTEM_ROOT/etc/network

# Copy hostcfgd files
sudo cp $IMAGE_CONFIGS/hostcfgd/hostcfgd.service $FILESYSTEM_ROOT/etc/systemd/system/
sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable hostcfgd.service
sudo cp $IMAGE_CONFIGS/hostcfgd/hostcfgd $FILESYSTEM_ROOT/usr/bin/
sudo cp $IMAGE_CONFIGS/hostcfgd/*.j2 $FILESYSTEM_ROOT/usr/share/sonic/templates/

# Copy hostname configuration scripts
sudo cp $IMAGE_CONFIGS/hostname/hostname-config.service $FILESYSTEM_ROOT/etc/systemd/system/
sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable hostname-config.service
Expand Down
43 changes: 43 additions & 0 deletions files/image_config/hostcfgd/common-auth-sonic.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# THIS IS AN AUTO-GENERATED FILE
#
# /etc/pam.d/common-auth- authentication settings common to all services
# This file is included from other service-specific PAM config files,
# and should contain a list of the authentication modules that define
# the central authentication scheme for use on the system
# (e.g., /etc/shadow, LDAP, Kerberos, etc.). The default is to use the
# traditional Unix authentication mechanisms.
#
# here are the per-package modules (the "Primary" block)

{% if auth['login'] == 'local' %}
auth [success=1 default=ignore] pam_unix.so nullok try_first_pass

{% elif auth['login'] == 'local,tacacs+' %}
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_unix.so nullok try_first_pass
{% for server in servers | sub(0, -1) %}
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_tacplus.so server={{ server.ip }}:{{ server.tcp_port }} secret={{ server.passkey }} login={{ server.auth_type }} timeout={{ server.timeout }} try_first_pass
{% endfor %}
{% if servers | count %}
{% set last_server = servers | last %}
auth [success=1 default=ignore] pam_tacplus.so server={{ last_server.ip }}:{{ last_server.tcp_port }} secret={{ last_server.passkey }} login={{ last_server.auth_type }} timeout={{ last_server.timeout }} try_first_pass

{% endif %}
{% elif auth['login'] == 'tacacs+' or auth['login'] == 'tacacs+,local' %}
{% for server in servers %}
auth [success=done new_authtok_reqd=done default=ignore{{ ' auth_err=die' if not auth['failthrough'] }}] pam_tacplus.so server={{ server.ip }}:{{ server.tcp_port }} secret={{ server.passkey }} login={{ server.auth_type }} timeout={{ server.timeout }} try_first_pass
{% endfor %}
auth [success=1 default=ignore] pam_unix.so nullok try_first_pass

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

{% endif %}
#
# here's the fallback if no module succeeds
auth requisite pam_deny.so
# prime the stack with a positive return value if there isn't one already;
# this avoids us returning an error just because nothing sets a success code
# since the modules above will each just jump around
auth required pam_permit.so
# and here are more per-package modules (the "Additional" block)

172 changes: 172 additions & 0 deletions files/image_config/hostcfgd/hostcfgd
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/python -u
# -*- coding: utf-8 -*-

import os
import sys
import subprocess
import syslog
import jinja2
from swsssdk import ConfigDBConnector

# FILE
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_CONF = "/etc/nsswitch.conf"

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


def is_true(val):
if val == 'True' or val == 'true':
return True
else:
return False


def sub(l, start, end):
return l[start:end]


class AaaCfg(object):
def __init__(self):
self.auth_default = {
'login': 'local',
'failthrough': True,
'fallback': True
}
self.tacplus_global_default = {
'auth_type': TACPLUS_SERVER_AUTH_TYPE_DEFAULT,
'timeout': TACPLUS_SERVER_TIMEOUT_DEFAULT,
'passkey': TACPLUS_SERVER_PASSKEY_DEFAULT
}
self.auth = {}
self.tacplus_global = {}
self.tacplus_servers = {}
self.debug = False

# Load conf from ConfigDb
def load(self, aaa_conf, tac_global_conf, tacplus_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)
self.modify_conf_file()

def aaa_update(self, key, data, modify_conf=True):
if key == 'authentication':
self.auth = data
if 'failthrough' in data:
self.auth['failthrough'] = is_true(data['failthrough'])
if 'debug' in data:
self.debug = is_true(data['debug'])
if modify_conf:
self.modify_conf_file()

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

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

if modify_conf:
self.modify_conf_file()

def modify_conf_file(self):
auth = self.auth_default.copy()
auth.update(self.auth)
tacplus_global = self.tacplus_global_default.copy()
tacplus_global.update(self.tacplus_global)

servers_conf = []
if self.tacplus_servers:
for addr in self.tacplus_servers:
server = tacplus_global.copy()
server['ip'] = addr
server.update(self.tacplus_servers[addr])
servers_conf.append(server)
sorted(servers_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)
with open(PAM_AUTH_CONF, 'w') as f:
f.write(pam_conf)

# Modify common-auth include file in /etc/pam.d/login and sshd
if os.path.isfile(PAM_AUTH_CONF):
os.system("sed -i -e '/^@include/s/common-auth$/common-auth-sonic/' /etc/pam.d/sshd")
os.system("sed -i -e '/^@include/s/common-auth$/common-auth-sonic/' /etc/pam.d/login")
else:
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
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")
else:
if os.path.isfile(NSS_CONF):
os.system("sed -i -e '/^passwd/s/ tacplus//' /etc/nsswitch.conf")

# Set tacacs+ server in nss-tacplus conf
template_file = os.path.abspath(NSS_TACPLUS_CONF_TEMPLATE)
template = env.get_template(template_file)
nss_tacplus_conf = template.render(debug=self.debug, servers=servers_conf)
with open(NSS_TACPLUS_CONF, 'w') as f:
f.write(nss_tacplus_conf)


class HostConfigDaemon:
def __init__(self):
self.config_db = ConfigDBConnector()
self.config_db.connect(wait_for_init=True, retry_on=True)
syslog.syslog(syslog.LOG_INFO, 'ConfigDB connect success')
aaa = self.config_db.get_table('AAA')
tacacs_global = self.config_db.get_table('TACPLUS')
tacacs_server = self.config_db.get_table('TACPLUS_SERVER')
self.aaacfg = AaaCfg()
self.aaacfg.load(aaa, tacacs_global, tacacs_server)

def aaa_handler(self, key, data):
syslog.syslog(syslog.LOG_DEBUG, 'value for {} changed to {}'.format(key, data))
self.aaacfg.aaa_update(key, data)

def tacacs_server_handler(self, key, data):
syslog.syslog(syslog.LOG_DEBUG, 'value for {} changed to {}'.format(key, data))
self.aaacfg.tacacs_server_update(key, data)

def tacacs_global_handler(self, key, data):
syslog.syslog(syslog.LOG_DEBUG, 'value for {} changed to {}'.format(key, data))
self.aaacfg.tacacs_global_update(key, data)

def start(self):
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.listen()


def main():
daemon = HostConfigDaemon()
daemon.start()


if __name__ == "__main__":
main()

11 changes: 11 additions & 0 deletions files/image_config/hostcfgd/hostcfgd.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=Host config enforcer daemon
Requires=database.service
After=database.service

[Service]
Type=simple
ExecStart=/usr/bin/hostcfgd

[Install]
WantedBy=multi-user.target
25 changes: 25 additions & 0 deletions files/image_config/hostcfgd/tacplus_nss.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
onfiguration for libnss-tacplus

# debug - If you want to open debug log, set it on
# Default: off
# debug=on
{% if debug %}
debug=on
{% endif %}

# server - set ip address, tcp port, secret string and timeout for TACACS+ servers
# Default: None (no TACACS+ server)
# server=1.1.1.1:49,secret=test,timeout=3
{% for server in servers %}
server={{ server.ip }}:{{ server.tcp_port }},secret={{ server.passkey }},timeout={{ server.timeout }}
{% endfor %}

# user_priv - set the map between TACACS+ user privilege and local user's passwd
# Default:
# user_priv=15;pw_info=remote_user_su;gid=1000;group=sudo,docker;shell=/bin/bash
# user_priv=1;pw_info=remote_user;gid=999;group=docker;shell=/bin/bash

# many_to_one - create one local user for many TACACS+ users which has the same privilege
# Default: many_to_one=n
# many_to_one=y