Remove Arista-specific service ACL solution; All platforms now use caclmgrd (#2202)

This commit is contained in:
Joe LeVeque 2018-10-29 10:25:18 -07:00 committed by GitHub
parent 6a37365d93
commit 1e1add90f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 3 additions and 941 deletions

View File

@ -246,9 +246,6 @@ sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y do
sudo mv $FILESYSTEM_ROOT/grub-pc-bin*.deb $FILESYSTEM_ROOT/$PLATFORM_DIR/x86_64-grub
sudo dpkg --root=$FILESYSTEM_ROOT -i target/debs/libwrap0_*.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
## Disable kexec supported reboot which was installed by default
sudo sed -i 's/LOAD_KEXEC=true/LOAD_KEXEC=false/' $FILESYSTEM_ROOT/etc/default/kexec

View File

@ -58,8 +58,6 @@ RUN rm -rf /debs /python-wheels ~/.cache
COPY ["start.sh", "/usr/bin/"]
COPY ["supervisord.conf", "/etc/supervisor/conf.d/"]
COPY ["*.j2", "/usr/share/sonic/templates/"]
COPY ["snmpd-config-updater", "/usr/bin/snmpd-config-updater"]
RUN chmod +x /usr/bin/snmpd-config-updater
# Although exposing ports is not needed for host net mode, keep it for possible bridge mode
EXPOSE 161/udp 162/udp

View File

@ -1,263 +0,0 @@
#!/usr/bin/env python
# Daemon that listens to updates from ConfigDB about the source IP prefixes from which
# SNMP connections are allowed. In case of change, it will update the SNMP configuration
# file accordingly. After a change, it will notify snmpd to re-read its config file
# via SIGHUP.
#
# This daemon is meant to be run on Arista platforms only. Service ACLs on all other
# platforms will be managed by caclmgrd.
#
import os
import re
import signal
import subprocess
import sys
import syslog
import time
from swsssdk import ConfigDBConnector
VERSION = "1.0"
SYSLOG_IDENTIFIER = "snmpd-config-updater"
# ============================== Classes ==============================
class Process(object):
def __init__(self, pid):
self.pid = pid
self.path = '/proc/%d/status' % pid
self.status = None
def read_proc_status(self):
data = {}
with open(self.path) as f:
for line in f.readlines():
key, value = line.split(':', 1)
data[ key ] = value.strip()
self.status = data
def get_proc_signals(self):
assert self.status
sigBlk = int(self.status[ 'SigBlk' ], 16)
sigIgn = int(self.status[ 'SigIgn' ], 16)
sigCgt = int(self.status[ 'SigCgt' ], 16)
return (sigBlk, sigIgn, sigCgt)
def handle_signal(self, sig):
sigBlk, sigIgn, sigCgt = self.get_proc_signals()
mask = 1 << ( sig - 1 )
if mask & sigBlk:
return True
if mask & sigIgn:
return True
if mask & sigCgt:
return True
return False
def send_signal(self, sig):
log_info('Sending signal %s to %d' % (sig, self.pid))
os.kill(self.pid, sig)
def safe_send_signal(self, sig):
self.read_proc_status()
if not self.handle_signal(sig):
return False
self.send_signal(sig)
return True
def wait_send_signal(self, sig, interval=0.1):
while not self.safe_send_signal(sig):
log_info('Process %s has not yet registered %s' % (self.pid, sig))
time.sleep(interval)
@staticmethod
def by_name(name):
try:
pid = subprocess.check_output([ 'pidof', '-s', name ])
except subprocess.CalledProcessError:
return None
return Process(int(pid.rstrip()))
class ConfigUpdater(object):
SERVICE = "snmpd"
CONFIG_FILE_PATH = "/etc/snmp"
ACL_TABLE = "ACL_TABLE"
ACL_RULE = "ACL_RULE"
ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE"
ACL_SERVICE_SNMP = "SNMP"
def get_src_ip_allow_list(self):
src_ip_allow_list = []
# Get current ACL tables and rules from Config DB
tables_db_info = self.config_db.get_table(self.ACL_TABLE)
rules_db_info = self.config_db.get_table(self.ACL_RULE)
# Walk the ACL tables
for (table_name, table_data) in tables_db_info.iteritems():
# Ignore non-control-plane ACL tables
if table_data["type"] != self.ACL_TABLE_TYPE_CTRLPLANE:
continue
# Ignore non-SNMP service ACLs
if self.ACL_SERVICE_SNMP not in table_data["services"]:
continue
acl_rules = {}
for ((rule_table_name, rule_id), rule_props) in rules_db_info.iteritems():
if rule_table_name == table_name:
acl_rules[rule_props["PRIORITY"]] = rule_props
# For each ACL rule in this table (in descending order of priority)
for priority in sorted(acl_rules.iterkeys(), reverse=True):
rule_props = acl_rules[priority]
if "PACKET_ACTION" not in rule_props:
log_error("ACL rule does not contain PACKET_ACTION property")
continue
# We're only interested in ACCEPT rules
if rule_props["PACKET_ACTION"] != "ACCEPT":
continue
if "SRC_IP" in rule_props and rule_props["SRC_IP"]:
src_ip_allow_list.append(rule_props["SRC_IP"])
return src_ip_allow_list
# To update the configuration file
#
# Example config file for reference:
# root@sonic:/# cat /etc/snmp/snmpd.conf
# <...some snmp config, like udp port to use etc...>
# rocommunity public 172.20.61.0/24
# rocommunity public 172.20.60.0/24
# rocommunity public 127.00.00.0/8
# <...some more snmp config...>
# root@sonic:/#
#
# snmpd.conf supports include file, like so:
# includeFile /etc/snmp/community.conf
# includeDir /etc/snmp/config.d
# which could make file massaging simpler, but even then we still deal with lines
# that have shared "masters", since some other entity controls the community strings
# part of that line.
# If other database attributes need to be written to the snmp config file, then
# it should be done by this daemon as well (sure, we could inotify on the file
# and correct it back, but that's glitchy).
#
# src_ip_allow_list may contain individual IP addresses or blocks of
# IP addresses using CIDR notation.
def write_configuration_file(self, src_ip_allow_list):
filename = "%s/%s.conf" % (self.CONFIG_FILE_PATH, self.SERVICE)
filename_tmp = filename + ".tmp"
f = open(filename, "r")
snmpd_config = f.read()
f.close()
f = open(filename_tmp, "w")
this_community = "not_a_community"
for line in snmpd_config.split('\n'):
m = re.match("^(..)community (\S+)", line)
if not m:
f.write(line)
f.write("\n")
else:
if not line.startswith(this_community): # already handled community (each community is duplicated per allow entry)
this_community = "%scommunity %s" % (m.group(1), m.group(2))
if len(src_ip_allow_list):
for value in src_ip_allow_list:
f.write("%s %s\n" % (this_community, value))
else:
f.write("%s\n" % this_community)
f.close()
os.rename(filename_tmp, filename)
# Force snmpd process to reload its configuration if it is running
proc = Process.by_name(self.SERVICE)
if proc:
proc.wait_send_signal(signal.SIGHUP)
def notification_handler(self, key, data):
log_info("ACL configuration changed. Updating {} config accordingly...".format(self.SERVICE))
self.write_configuration_file(self.get_src_ip_allow_list())
def run(self):
# Open a handle to the Config database
self.config_db = ConfigDBConnector()
self.config_db.connect()
# Write initial configuration
self.write_configuration_file(self.get_src_ip_allow_list())
# Subscribe to notifications when ACL tables or rules change
self.config_db.subscribe(self.ACL_TABLE,
lambda table, key, data: self.notification_handler(key, data))
self.config_db.subscribe(self.ACL_RULE,
lambda table, key, data: self.notification_handler(key, data))
# Indefinitely listen for Config DB notifications
self.config_db.listen()
# ========================== Syslog wrappers ==========================
def log_info(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_INFO, msg)
syslog.closelog()
def log_warning(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_WARNING, msg)
syslog.closelog()
def log_error(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_ERR, msg)
syslog.closelog()
# Determine whether we are running on an Arista platform
def is_platform_arista():
proc = subprocess.Popen(["sonic-cfggen", "-H", "-v", "DEVICE_METADATA.localhost.platform"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = proc.communicate()
if proc.returncode != 0:
log_error("Failed to retrieve platform string")
return False
return "arista" in stdout
def main():
log_info("Starting up...")
if not os.geteuid() == 0:
log_error("Must be root to run this daemon")
print "Error: Must be root to run this daemon"
sys.exit(1)
if not is_platform_arista():
log_info("Platform is not an Arista platform. Exiting...")
sys.exit(0)
# Instantiate a ConfigUpdater object
config_updater = ConfigUpdater()
config_updater.run()
if __name__ == "__main__":
main()

View File

@ -12,6 +12,5 @@ echo "# Config files managed by sonic-config-engine" > /var/sonic/config_status
rm -f /var/run/rsyslogd.pid
supervisorctl start rsyslogd
supervisorctl start snmpd-config-updater
supervisorctl start snmpd
supervisorctl start snmp-subagent

View File

@ -19,18 +19,9 @@ autorestart=false
stdout_logfile=syslog
stderr_logfile=syslog
[program:snmpd-config-updater]
command=/usr/bin/snmpd-config-updater
priority=3
autostart=false
autorestart=unexpected
startsecs=0
stdout_logfile=syslog
stderr_logfile=syslog
[program:snmpd]
command=/usr/sbin/snmpd -f -LS4d -u Debian-snmp -g Debian-snmp -I -smux,mteTrigger,mteTriggerConf,ifTable,ifXTable,inetCidrRouteTable,ipCidrRouteTable,ip,disk_hw -p /run/snmpd.pid
priority=4
priority=3
autostart=false
autorestart=false
stdout_logfile=syslog
@ -38,7 +29,7 @@ stderr_logfile=syslog
[program:snmp-subagent]
command=/usr/bin/env python3.6 -m sonic_ax_impl
priority=5
priority=4
autostart=false
autorestart=false
stdout_logfile=syslog

View File

@ -206,7 +206,6 @@ sudo cp $IMAGE_CONFIGS/sudoers/sudoers.lecture $FILESYSTEM_ROOT/etc/
# Copy control plane ACL management daemon files
sudo cp $IMAGE_CONFIGS/caclmgrd/caclmgrd.service $FILESYSTEM_ROOT/etc/systemd/system/
sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable caclmgrd.service
sudo cp $IMAGE_CONFIGS/caclmgrd/caclmgrd-start.sh $FILESYSTEM_ROOT/usr/bin/
sudo cp $IMAGE_CONFIGS/caclmgrd/caclmgrd $FILESYSTEM_ROOT/usr/bin/
## Install package without starting service
@ -259,21 +258,6 @@ if [ "$image_type" = "aboot" ]; then
sudo sed -i 's/udevadm settle/udevadm settle -E \/sys\/class\/net\/eth0/' $FILESYSTEM_ROOT/etc/init.d/networking
fi
# Service to update the sshd config file based on database changes for Arista devices
sudo cp $IMAGE_CONFIGS/ssh/sshd-config-updater.service $FILESYSTEM_ROOT/etc/systemd/system
sudo mkdir -p $FILESYSTEM_ROOT/etc/systemd/system/multi-user.target.wants
cd $FILESYSTEM_ROOT/etc/systemd/system/multi-user.target.wants/
sudo ln -s ../sshd-config-updater.service sshd-config-updater.service
cd -
sudo cp $IMAGE_CONFIGS/ssh/sshd-config-updater $FILESYSTEM_ROOT/usr/bin/
sudo chmod +x $FILESYSTEM_ROOT/usr/bin/sshd-config-updater
sudo cp $IMAGE_CONFIGS/ssh/sshd-clear-denied-sessions $FILESYSTEM_ROOT/usr/bin
sudo chmod +x $FILESYSTEM_ROOT/usr/bin/sshd-clear-denied-sessions
sudo dpkg --root=$FILESYSTEM_ROOT -i target/debs/libwrap0_*_amd64.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
sudo dpkg --root=$FILESYSTEM_ROOT -i target/debs/tcpd_*_amd64.deb || \
sudo LANG=C DEBIAN_FRONTEND=noninteractive chroot $FILESYSTEM_ROOT apt-get -y install -f
## copy platform rc.local
sudo cp $IMAGE_CONFIGS/platform/rc.local $FILESYSTEM_ROOT/etc/

View File

@ -1,10 +0,0 @@
#!/usr/bin/env bash
# Only start control plance ACL manager daemon if not an Arista platform.
# Arista devices will use their own service ACL manager daemon(s) instead.
if [ "$(sonic-cfggen -H -v "DEVICE_METADATA.localhost.platform" | grep -c "arista")" -gt 0 ]; then
echo "Not starting caclmgrd - unsupported platform"
exit 0
fi
exec /usr/bin/caclmgrd

View File

@ -5,7 +5,7 @@ After=updategraph.service
[Service]
Type=simple
ExecStart=/usr/bin/caclmgrd-start.sh
ExecStart=/usr/bin/caclmgrd
[Install]
WantedBy=multi-user.target

View File

@ -1,81 +0,0 @@
#!/usr/bin/env python
"""
This utility will find the ip addresses of all hosts that have connected to
this device via ssh, then validate they are still in the list of allowed prefixes,
and if not kill the ssh session with a SIGHUP.
"""
import os
import re
import subprocess
# Run utmpdump, capture and return its output
def run_utmpdump(_utmpFilename):
devnull = file("/dev/null", "w" )
p = subprocess.Popen(args=["utmpdump", _utmpFilename], stdout=subprocess.PIPE, stderr=devnull)
(stdout, stderr) = p.communicate()
rc = p.returncode
assert rc is not None # because p.communicate() should wait.
out = (stdout or '') + (stderr or '')
if rc:
e = SystemCommandError("%r: error code %d" % (" ".join(argv), rc))
e.error = rc
e.output = out
raise e
return stdout
# Run utmpdump and parse its output into a list of dicts and return that
def get_utmp_data(utmpFileName=None):
"""Reads the specified utmp file.
Returns a list of dictionaries, one for each utmp entry.
All dictionary keys and values are strings
Values are right padded with spaces and may contain all
spaces if that utmp field is empty.
Dictionary keys:
"type": See UTMP_TYPE_* above
"pid": Process ID as a string
"tty": TTY (line) name - device name of tty w/o "/dev/"
"tty4": 4 char abbreivated TTY (line) name
"user": User ID
"host": Hostname for remote login,
kernel release for Run Level and Boot Time
"ipAddr": IP Address
"time": Time and date entry was made
See linux docs on utmp and utmpdemp for more info.
Example output from utmpdump:
pid tty4 user tty host ipAddr time
[7] [22953] [/238] [myname ] [pts/238 ] [example.com] [253.122.98.159 ] [Mon Dec 18 21:08:09 2017 PST]
"""
if not utmpFileName:
utmpFileName = os.environ.get( "DEFAULT_UTMP_FILE", "/var/run/utmp" )
if not os.path.exists(utmpFileName):
return []
output = run_utmpdump(utmpFileName)
lines = re.split("\n", output)
regExp = re.compile(
r"\[(?P<type>" r"[^\]]*?)\s*\] \[(?P<pid>" r"[^\]]*?)\s*\] " \
r"\[(?P<tty4>" r"[^\]]*?)\s*\] \[(?P<user>" r"[^\]]*?)\s*\] " \
r"\[(?P<tty>" r"[^\]]*?)\s*\] \[(?P<host>" r"[^\]]*?)\s*\] " \
r"\[(?P<ipAddr>" r"[^\]]*?)\s*\] \[(?P<time>" r"[^\]]*?)\s*\]" )
entries = []
for line in lines:
m = regExp.match(line)
if not m:
# Skip header and any other lines we don't recognize
continue
entry = m.groupdict()
entries.append(entry)
return entries
# Find the source ip addresses of all ssh sessions, verify they are still allowed, and if not kill the ssh session
if __name__ == '__main__':
for e in get_utmp_data():
if e["host"] and e["ipAddr"] != "0.0.0.0": # entry is for a live connection
# check allowness
r = os.system('tcpdmatch sshd %s | grep "access.*granted" > /dev/null' % e["ipAddr"])
# print some debugs
print "From:", e["ipAddr"], "ssh pid:", e["pid"], "allowed" if r == 0 else "denied"
# if needed go for the kill
if r != 0:
os.system("kill -1 %s" % e["pid"])

View File

@ -1,185 +0,0 @@
#!/usr/bin/env python
# Daemon that listens to updates from ConfigDB about the source IP prefixes from which
# SSH connections are allowed. In case of change, it will update the SSHD configuration
# file accordingly. SSHD will notice the file has changed next time a connection comes in.
# Future enhancement: if an entry it modified/removed, go through all existing ssh
# connections and recompute their permission, and in case one is now denied, kill it.
#
# This daemon is meant to be run on Arista platforms only. Service ACLs on all other
# platforms will be managed by caclmgrd.
#
import os
import subprocess
import sys
import syslog
import time
from swsssdk import ConfigDBConnector
VERSION = "1.0"
SYSLOG_IDENTIFIER = "sshd-config-updater"
# ============================== Classes ==============================
class ConfigUpdater(object):
SERVICE = "sshd"
CONFIG_FILE_PATH = "/etc"
ACL_TABLE = "ACL_TABLE"
ACL_RULE = "ACL_RULE"
ACL_TABLE_TYPE_CTRLPLANE = "CTRLPLANE"
ACL_SERVICE_SSH = "SSH"
def get_src_ip_allow_list(self):
src_ip_allow_list = []
# Get current ACL tables and rules from Config DB
tables_db_info = self.config_db.get_table(self.ACL_TABLE)
rules_db_info = self.config_db.get_table(self.ACL_RULE)
# Walk the ACL tables
for (table_name, table_data) in tables_db_info.iteritems():
# Ignore non-control-plane ACL tables
if table_data["type"] != self.ACL_TABLE_TYPE_CTRLPLANE:
continue
# Ignore non-SSH service ACLs
if self.ACL_SERVICE_SSH not in table_data["services"]:
continue
acl_rules = {}
for ((rule_table_name, rule_id), rule_props) in rules_db_info.iteritems():
if rule_table_name == table_name:
acl_rules[rule_props["PRIORITY"]] = rule_props
# For each ACL rule in this table (in descending order of priority)
for priority in sorted(acl_rules.iterkeys(), reverse=True):
rule_props = acl_rules[priority]
if "PACKET_ACTION" not in rule_props:
log_error("ACL rule does not contain PACKET_ACTION property")
continue
# We're only interested in ACCEPT rules
if rule_props["PACKET_ACTION"] != "ACCEPT":
continue
if "SRC_IP" in rule_props and rule_props["SRC_IP"]:
src_ip_allow_list.append(rule_props["SRC_IP"])
return src_ip_allow_list
# To update the configuration file
#
# Example config file for reference:
# root@sonic:/# cat /etc/snmp/snmpd.conf
# bash# cat /etc/sshd.allow
# sshd: [fd7a:629f:52a4:b0c3:ec4:7aff:fe99:201e]/128
# sshd: 172.17.0.1/32
# sshd: 172.18.1.0/24
# Note that any matches are 'permits', and the default action is 'denied'
# We assume the database contains valid ip addresses/hostnames.
#
# src_ip_allow_list may contain individual IP addresses or blocks of
# IP addresses using CIDR notation.
def write_configuration_file(self, src_ip_allow_list):
filename = "%s/%s.allow" % (self.CONFIG_FILE_PATH, self.SERVICE)
if len(src_ip_allow_list) == 0:
if os.path.exists(filename):
os.remove(filename)
return
filename_tmp = filename + ".tmp"
f = open(filename_tmp, "w")
for value in src_ip_allow_list:
f.write("%s: %s\n" % (self.SERVICE, value))
f.close()
os.rename(filename_tmp, filename)
# some previously accepted sessions might no longer be allowed: clear them
os.system("/usr/bin/sshd-clear-denied-sessions")
def notification_handler(self, key, data):
log_info("ACL configuration changed. Updating {} config accordingly...".format(self.SERVICE))
self.write_configuration_file(self.get_src_ip_allow_list())
def run(self):
# Open a handle to the Config database
self.config_db = ConfigDBConnector()
self.config_db.connect()
# Write initial configuration
self.write_configuration_file(self.get_src_ip_allow_list())
# Subscribe to notifications when ACL tables or rules change
self.config_db.subscribe(self.ACL_TABLE,
lambda table, key, data: self.notification_handler(key, data))
self.config_db.subscribe(self.ACL_RULE,
lambda table, key, data: self.notification_handler(key, data))
# Indefinitely listen for Config DB notifications
self.config_db.listen()
# ========================== Syslog wrappers ==========================
def log_info(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_INFO, msg)
syslog.closelog()
def log_warning(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_WARNING, msg)
syslog.closelog()
def log_error(msg):
syslog.openlog(SYSLOG_IDENTIFIER)
syslog.syslog(syslog.LOG_ERR, msg)
syslog.closelog()
# Determine whether we are running on an Arista platform
def is_platform_arista():
proc = subprocess.Popen(["sonic-cfggen", "-H", "-v", "DEVICE_METADATA.localhost.platform"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
(stdout, stderr) = proc.communicate()
if proc.returncode != 0:
log_error("Failed to retrieve platform string")
return false
return "arista" in stdout
def main():
log_info("Starting up...")
if not os.geteuid() == 0:
log_error("Must be root to run this daemon")
print "Error: Must be root to run this daemon"
sys.exit(1)
if not is_platform_arista():
log_info("Platform is not an Arista platform. Exiting...")
sys.exit(0)
# Instantiate a ConfigUpdater object
config_updater = ConfigUpdater()
config_updater.run()
if __name__ == "__main__":
main()

View File

@ -1,14 +0,0 @@
[Unit]
Description=Takes care of updates to SSH config file with respect to the SSH allow list
After=updategraph.service
Requires=updategraph.service
ConditionPathExists=!/etc/ssh/sshd_not_to_be_run
[Service]
ExecStart=/usr/bin/sshd-config-updater
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
[Install]
WantedBy=multi-user.target

View File

@ -3,7 +3,6 @@
DOCKER_BASE = docker-base.gz
$(DOCKER_BASE)_PATH = $(DOCKERS_PATH)/docker-base
$(DOCKER_BASE)_DEPENDS += $(SUPERVISOR)
$(DOCKER_BASE)_DEPENDS += $(LIBWRAP)
$(DOCKER_BASE)_DEPENDS += $(BASH)
$(DOCKER_BASE)_DEPENDS += $(SOCAT)

View File

@ -12,6 +12,5 @@ SONIC_INSTALL_DOCKER_IMAGES += $(DOCKER_SNMP_SV2)
$(DOCKER_SNMP_SV2)_CONTAINER_NAME = snmp
$(DOCKER_SNMP_SV2)_RUN_OPT += --net=host --privileged -t
$(DOCKER_SNMP_SV2)_RUN_OPT += -v /etc/sonic:/etc/sonic:ro
$(DOCKER_SNMP_SV2)_RUN_OPT += -v /host/machine.conf:/host/machine.conf
# mount Arista platform python libraries to support corresponding platforms SNMP power status query
$(DOCKER_SNMP_SV2)_RUN_OPT += -v /usr/lib/python3/dist-packages/arista:/usr/lib/python3/dist-packages/arista:ro

View File

@ -1,14 +0,0 @@
# libwrap packages
LIBWRAP_VERSION = 7.6.q-26
export LIBWRAP_VERSION
LIBWRAP = libwrap0_$(LIBWRAP_VERSION)_amd64.deb
$(LIBWRAP)_SRC_PATH = $(SRC_PATH)/libwrap
SONIC_MAKE_DEBS += $(LIBWRAP)
TCPD = tcpd_$(LIBWRAP_VERSION)_amd64.deb
$(eval $(call add_derived_package,$(LIBWRAP),$(TCPD)))
SONIC_STRETCH_DEBS += $(LIBWRAP)

View File

@ -475,7 +475,6 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \
$(PYTHON_CLICK) \
$(SONIC_UTILS) \
$(BASH) \
$(LIBWRAP) \
$(LIBPAM_TACPLUS) \
$(LIBNSS_TACPLUS)) \
$$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \

View File

@ -1,31 +0,0 @@
.ONESHELL:
SHELL = /bin/bash
.SHELLFLAGS += -e
MAIN_TARGET = libwrap0_$(LIBWRAP_VERSION)_amd64.deb
DERIVED_TARGETS = tcpd_$(LIBWRAP_VERSION)_amd64.deb
$(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% :
# Remove any stale files
rm -rf ./tcp-wrappers-7.6.q*
# Get source package
wget -NO tcp-wrappers_$(LIBWRAP_VERSION).dsc "https://sonicstorage.blob.core.windows.net/packages/debian/tcp-wrappers_7.6.q-26.dsc?sv=2015-04-05&sr=b&sig=5dxVp8RsLfnuMDm99g4JpgG6Q3zzTaoTkPNVoo0d8YE%3D&se=2155-07-05T06%3A34%3A45Z&sp=r"
wget -NO tcp-wrappers_$(LIBWRAP_VERSION).debian.tar.xz "https://sonicstorage.blob.core.windows.net/packages/debian/tcp-wrappers_7.6.q-26.debian.tar.xz?sv=2015-04-05&sr=b&sig=6%2FquqekmGloVz%2FaDc%2BCOCLL%2FKqEfIWrr%2BdmnZhJ8k8Q%3D&se=2155-07-05T06%3A32%3A43Z&sp=r"
wget -NO tcp-wrappers_7.6.q.orig.tar.gz "https://sonicstorage.blob.core.windows.net/packages/debian/tcp-wrappers_7.6.q.orig.tar.gz?sv=2015-04-05&sr=b&sig=%2B%2BQbllR9xfwmYvgLiz1jtyn6ZP4oLgbTENTL5Fj8M4w%3D&se=2155-07-05T06%3A35%3A24Z&sp=r"
dpkg-source -x tcp-wrappers_$(LIBWRAP_VERSION).dsc
cp tcp_wrappers-7.6-allowlist.patch tcp-wrappers-7.6.q/debian/patches/tcp_wrappers-7.6-allowlist.patch
echo tcp_wrappers-7.6-allowlist.patch >> tcp-wrappers-7.6.q/debian/patches/series
# Build source and Debian packages
pushd tcp-wrappers-7.6.q
dpkg-buildpackage -rfakeroot -b -us -uc -j$(SONIC_CONFIG_MAKE_JOBS)
popd
# Move the newly-built .deb packages to the destination directory
mv $(DERIVED_TARGETS) $* $(DEST)/
$(addprefix $(DEST)/, $(DERIVED_TARGETS)): $(DEST)/% : $(DEST)/$(MAIN_TARGET)

View File

@ -1,306 +0,0 @@
--- a/debian/libwrap0.symbols 2017-11-15 21:25:07.631105657 +0000
+++ b/debian/libwrap0.symbols 2017-11-16 00:19:41.156453123 +0000
@@ -9,6 +9,7 @@ libwrap.so.0 libwrap0 #MINVER#
eval_hostaddr@Base 7.6-4~
eval_hostinfo@Base 7.6-4~
eval_hostname@Base 7.6-4~
+ eval_hostport@Base 7.6.q-25
eval_port@Base 7.6-4~
eval_server@Base 7.6-4~
eval_user@Base 7.6-4~
@@ -32,6 +33,7 @@ libwrap.so.0 libwrap0 #MINVER#
sock_host@Base 7.6-4~
sock_hostaddr@Base 7.6-4~
sock_hostname@Base 7.6-4~
+ sock_hostport@Base 7.6.q-25
split_at@Base 7.6-4~
tcpd_buf@Base 7.6-4~
tcpd_context@Base 7.6-4~
diff -up a/eval.c b/eval.c
--- a/eval.c 2017-11-15 21:25:07.631105657 +0000
+++ b/eval.c 2017-11-15 21:41:00.597460658 +0000
@@ -66,6 +66,18 @@ struct host_info *host;
return (host->addr);
}
+/* eval_hostport - look up host port */
+char *eval_hostport(host)
+struct host_info *host;
+{
+ if (host->port[0] == 0) {
+ strcpy(host->port, unknown);
+ if (host->request->hostport != 0)
+ host->request->hostport(host);
+ }
+ return (host->port);
+}
+
/* eval_hostname - look up host name */
char *eval_hostname(host)
diff -up a/hosts_access.c b/hosts_access.c
--- a/hosts_access.c 2017-11-15 21:25:07.631105657 +0000
+++ b/hosts_access.c 2017-11-15 21:51:44.804345530 +0000
@@ -39,6 +39,10 @@ static char sccsid[] = "@(#) hosts_acces
#include <errno.h>
#include <setjmp.h>
#include <string.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <unistd.h>
#ifdef INET6
#include <netdb.h>
#endif
@@ -89,6 +93,7 @@ int aclexec_matched = 0;
/* Forward declarations. */
static int table_match();
+static void update_counters();
static int list_match();
static int server_match();
static int client_match();
@@ -130,6 +135,26 @@ struct request_info *request;
if (resident <= 0)
resident++;
verdict = setjmp(tcpd_buf);
+ if (request->daemon[0] != '\0') {
+ char daemon_access_table[STRING_LENGTH];
+ /* contruct an access table name based on the daemon name. If it exists
+ * consult it rather than the standard hosts.allow/hosts.deny files.
+ */
+ snprintf(daemon_access_table, sizeof(daemon_access_table)-1,
+ "/etc/%s.allow", request->daemon);
+ if (access(daemon_access_table, R_OK) == 0) {
+ /* if we longjmp'ed back from earlier setjmp due to an explict allow/deny
+ * action, then update counters and return here
+ */
+ if (verdict != 0) {
+ update_counters(request->daemon);
+ return (verdict == AC_PERMIT);
+ }
+ int match = table_match(daemon_access_table, request);
+ update_counters(request->daemon);
+ return (match == YES) ? (YES) : (NO);
+ }
+ }
if (verdict != 0)
return (verdict == AC_PERMIT);
if (table_match(hosts_allow_table, request) == YES)
@@ -162,6 +187,7 @@ struct request_info *request;
if ((fp = fopen(table, "r")) != 0) {
tcpd_context.file = table;
tcpd_context.line = 0;
+ tcpd_context.rule = 0;
while (match == NO && xgets(sv_list, sizeof(sv_list), fp) != 0) {
if (sv_list[strlen(sv_list) - 1] != '\n') {
tcpd_warn("missing newline or line too long");
@@ -178,6 +204,7 @@ struct request_info *request;
sh_cmd = split_at(cl_list, ':');
match = list_match(sv_list, request, server_match)
&& list_match(cl_list, request, client_match);
+ tcpd_context.rule++;
}
(void) fclose(fp);
} else if (errno != ENOENT) {
@@ -202,11 +229,59 @@ struct request_info *request;
shell_cmd(percent_x(cmd, sizeof(cmd), sh_cmd, request));
#endif
}
+ } else {
+ /* restore the context if no match */
+ tcpd_context = saved_context;
+ tcpd_context.rule = 0;
}
- tcpd_context = saved_context;
return (match);
}
+/* update_counters - open a daemon/rule specific counter file and increment its
+ * counter by one.
+ */
+void update_counters(daemon_name)
+const char *daemon_name;
+{
+ char counter_file[STRING_LENGTH];
+ FILE *fp;
+ int count = 0;
+
+ snprintf(counter_file, sizeof(counter_file)-1, "/tmp/%s-cnt", daemon_name);
+ /* put the counters in a per-daemon directory in /tmp */
+ if (access( counter_file, W_OK)) {
+ if (mkdir(counter_file, 0777) < 0) {
+ return;
+ }
+ }
+
+ /* no rule matched */
+ if (tcpd_context.rule == 0) {
+ snprintf(counter_file, sizeof(counter_file)-1, "/tmp/%s-cnt/norule",
+ daemon_name);
+ } else {
+ snprintf(counter_file, sizeof(counter_file)-1, "/tmp/%s-cnt/%d", daemon_name,
+ tcpd_context.rule);
+ }
+
+ /* try openining the counter file if it exists - if not create it */
+ fp = fopen(counter_file, "r+");
+ if (fp == NULL) {
+ fp = fopen(counter_file, "w+");
+ }
+ /* Lock the file to prevent conflcting read/modify/write */
+ if (flock(fileno(fp), LOCK_EX) < 0) {
+ return;
+ }
+ fscanf(fp, "%d\n", &count);
+ count++;
+ rewind(fp);
+ fprintf(fp, "%d\n", count );
+ fflush(fp);
+ flock(fileno(fp), LOCK_UN);
+ fclose(fp);
+}
+
/* list_match - match a request against a list of patterns with exceptions */
static int list_match(list, request, match_fn)
diff -up a/options.c b/options.c
--- a/options.c 2017-11-15 21:25:07.631105657 +0000
+++ b/options.c 2017-11-15 22:15:08.593913199 +0000
@@ -88,6 +88,7 @@ static void severity_option(); /* execu
static void allow_option(); /* execute "allow" option */
static void deny_option(); /* execute "deny" option */
static void banners_option(); /* execute "banners path" option */
+static void log_option(); /* execute "log value" option */
/* Structure of the options table. */
@@ -128,6 +129,7 @@ static struct option option_table[] = {
"allow", allow_option, USE_LAST,
"deny", deny_option, USE_LAST,
"banners", banners_option, NEED_ARG,
+ "log", log_option, NEED_ARG | EXPAND_ARG,
0,
};
@@ -504,6 +506,18 @@ struct request_info *request;
tcpd_jump("memory allocation failure");
}
+/* log_option - do syslog */
+
+/* ARGSUSED */
+
+static void log_option(value, request)
+const char *value;
+struct request_info *request;
+{
+ /* use allow_severity which is LOG_INFO by default */
+ syslog(allow_severity, value, 0);
+}
+
/*
* The severity option goes last because it comes with a huge amount of ugly
* #ifdefs and tables.
@@ -539,6 +553,10 @@ static struct syslog_names log_fac[] = {
#ifdef LOG_UUCP
"uucp", LOG_UUCP,
#endif
+#ifndef LOG_AUTHPRIV
+#define LOG_AUTHPRIV (10<<3)
+#endif
+ { "authpriv", LOG_AUTHPRIV },
#ifdef LOG_CRON
"cron", LOG_CRON,
#endif
diff -up a/percent_x.c b/percent_x.c
--- a/percent_x.c 2017-11-15 21:25:07.631105657 +0000
+++ b/percent_x.c 2017-11-15 21:41:00.597460658 +0000
@@ -56,6 +56,8 @@ ABCDEFGHIJKLMNOPQRSTUVWXYZ";
expansion =
ch == 'a' ? eval_hostaddr(request->client) :
ch == 'A' ? eval_hostaddr(request->server) :
+ ch == 't' ? eval_hostport(request->client) :
+ ch == 'T' ? eval_hostport(request->server) :
ch == 'c' ? eval_client(request) :
ch == 'd' ? eval_daemon(request) :
ch == 'h' ? eval_hostinfo(request->client) :
diff -up a/socket.c b/socket.c
--- a/socket.c 2017-11-15 21:25:07.631105657 +0000
+++ b/socket.c 2017-11-15 21:41:00.597460658 +0000
@@ -178,6 +178,28 @@ struct host_info *host;
#endif
}
+/* sock_hostport - get host port */
+void sock_hostport(host)
+struct host_info *host;
+{
+ unsigned short port;
+#ifdef INET6
+ struct sockaddr *sin = host->sin;
+ if (!sin)
+ return;
+
+ port = (sin->sa_family == AF_INET) ? ntohs(((struct sockaddr_in*)sin)->sin_port)
+ : ntohs(((struct sockaddr_in6*)sin)->sin6_port);
+#else
+ struct sockaddr_in *sin = host->sin;
+ if (!sin)
+ return;
+
+ port = ntohs(sin->sin_port);
+#endif
+ sprintf(host->port, "%hu", port);
+}
+
/* sock_hostname - map endpoint address to host name */
void sock_hostname(host)
diff -up a/tcpd.h b/tcpd.h
--- a/tcpd.h 2017-11-15 21:25:07.631105657 +0000
+++ b/tcpd.h 2017-11-15 21:59:43.507517375 +0000
@@ -20,6 +20,7 @@ __BEGIN_DECLS
struct host_info {
char name[STRING_LENGTH]; /* access via eval_hostname(host) */
char addr[STRING_LENGTH]; /* access via eval_hostaddr(host) */
+ char port[8]; /* access via eval_hostport(host) */
#ifdef INET6
struct sockaddr *sin; /* socket address or 0 */
#else
@@ -41,6 +42,7 @@ struct request_info {
void (*sink) (int); /* datagram sink function or 0 */
void (*hostname) (struct host_info *); /* address to printable hostname */
void (*hostaddr) (struct host_info *); /* address to printable address */
+ void (*hostport) (struct host_info *); /* address to printable port */
void (*cleanup) (struct request_info *); /* cleanup function or 0 */
struct netconfig *config; /* netdir handle */
};
@@ -147,6 +149,7 @@ extern char *eval_user(struct request_in
extern char *eval_hostname(struct host_info *); /* printable hostname */
extern char *eval_hostaddr(struct host_info *); /* printable host address */
extern char *eval_hostinfo(struct host_info *); /* host name or address */
+extern char *eval_hostport(struct host_info *); /* printable host port */
extern char *eval_client(struct request_info *);/* whatever is available */
extern char *eval_server(struct request_info *);/* whatever is available */
#ifdef INET6
@@ -165,8 +168,10 @@ extern void sock_host(struct request_inf
extern void sock_hostname(struct host_info *);
/* address to printable address */
extern void sock_hostaddr(struct host_info *);
+/* address to printable port */
+extern void sock_hostport (struct host_info *);
#define sock_methods(r) \
- { (r)->hostname = sock_hostname; (r)->hostaddr = sock_hostaddr; }
+ { (r)->hostname = sock_hostname; (r)->hostaddr = sock_hostaddr; (r)->hostport = sock_hostport; }
/* The System V Transport-Level Interface (TLI) interface. */
@@ -191,6 +196,7 @@ extern void tcpd_jump();
struct tcpd_context {
char *file; /* current file */
int line; /* current line */
+ int rule; /* matched rule */
};
extern struct tcpd_context tcpd_context;