Remove Arista-specific service ACL solution; All platforms now use caclmgrd (#2202)
This commit is contained in:
parent
6a37365d93
commit
1e1add90f9
@ -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 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
|
## Disable kexec supported reboot which was installed by default
|
||||||
sudo sed -i 's/LOAD_KEXEC=true/LOAD_KEXEC=false/' $FILESYSTEM_ROOT/etc/default/kexec
|
sudo sed -i 's/LOAD_KEXEC=true/LOAD_KEXEC=false/' $FILESYSTEM_ROOT/etc/default/kexec
|
||||||
|
|
||||||
|
@ -58,8 +58,6 @@ RUN rm -rf /debs /python-wheels ~/.cache
|
|||||||
COPY ["start.sh", "/usr/bin/"]
|
COPY ["start.sh", "/usr/bin/"]
|
||||||
COPY ["supervisord.conf", "/etc/supervisor/conf.d/"]
|
COPY ["supervisord.conf", "/etc/supervisor/conf.d/"]
|
||||||
COPY ["*.j2", "/usr/share/sonic/templates/"]
|
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
|
# Although exposing ports is not needed for host net mode, keep it for possible bridge mode
|
||||||
EXPOSE 161/udp 162/udp
|
EXPOSE 161/udp 162/udp
|
||||||
|
@ -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()
|
|
@ -12,6 +12,5 @@ echo "# Config files managed by sonic-config-engine" > /var/sonic/config_status
|
|||||||
rm -f /var/run/rsyslogd.pid
|
rm -f /var/run/rsyslogd.pid
|
||||||
|
|
||||||
supervisorctl start rsyslogd
|
supervisorctl start rsyslogd
|
||||||
supervisorctl start snmpd-config-updater
|
|
||||||
supervisorctl start snmpd
|
supervisorctl start snmpd
|
||||||
supervisorctl start snmp-subagent
|
supervisorctl start snmp-subagent
|
||||||
|
@ -19,18 +19,9 @@ autorestart=false
|
|||||||
stdout_logfile=syslog
|
stdout_logfile=syslog
|
||||||
stderr_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]
|
[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
|
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
|
autostart=false
|
||||||
autorestart=false
|
autorestart=false
|
||||||
stdout_logfile=syslog
|
stdout_logfile=syslog
|
||||||
@ -38,7 +29,7 @@ stderr_logfile=syslog
|
|||||||
|
|
||||||
[program:snmp-subagent]
|
[program:snmp-subagent]
|
||||||
command=/usr/bin/env python3.6 -m sonic_ax_impl
|
command=/usr/bin/env python3.6 -m sonic_ax_impl
|
||||||
priority=5
|
priority=4
|
||||||
autostart=false
|
autostart=false
|
||||||
autorestart=false
|
autorestart=false
|
||||||
stdout_logfile=syslog
|
stdout_logfile=syslog
|
||||||
|
@ -206,7 +206,6 @@ sudo cp $IMAGE_CONFIGS/sudoers/sudoers.lecture $FILESYSTEM_ROOT/etc/
|
|||||||
# Copy control plane ACL management daemon files
|
# Copy control plane ACL management daemon files
|
||||||
sudo cp $IMAGE_CONFIGS/caclmgrd/caclmgrd.service $FILESYSTEM_ROOT/etc/systemd/system/
|
sudo cp $IMAGE_CONFIGS/caclmgrd/caclmgrd.service $FILESYSTEM_ROOT/etc/systemd/system/
|
||||||
sudo LANG=C chroot $FILESYSTEM_ROOT systemctl enable caclmgrd.service
|
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/
|
sudo cp $IMAGE_CONFIGS/caclmgrd/caclmgrd $FILESYSTEM_ROOT/usr/bin/
|
||||||
|
|
||||||
## Install package without starting service
|
## 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
|
sudo sed -i 's/udevadm settle/udevadm settle -E \/sys\/class\/net\/eth0/' $FILESYSTEM_ROOT/etc/init.d/networking
|
||||||
fi
|
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
|
## copy platform rc.local
|
||||||
sudo cp $IMAGE_CONFIGS/platform/rc.local $FILESYSTEM_ROOT/etc/
|
sudo cp $IMAGE_CONFIGS/platform/rc.local $FILESYSTEM_ROOT/etc/
|
||||||
|
|
||||||
|
@ -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
|
|
@ -5,7 +5,7 @@ After=updategraph.service
|
|||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
Type=simple
|
Type=simple
|
||||||
ExecStart=/usr/bin/caclmgrd-start.sh
|
ExecStart=/usr/bin/caclmgrd
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
WantedBy=multi-user.target
|
||||||
|
@ -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"])
|
|
@ -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()
|
|
@ -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
|
|
@ -3,7 +3,6 @@
|
|||||||
DOCKER_BASE = docker-base.gz
|
DOCKER_BASE = docker-base.gz
|
||||||
$(DOCKER_BASE)_PATH = $(DOCKERS_PATH)/docker-base
|
$(DOCKER_BASE)_PATH = $(DOCKERS_PATH)/docker-base
|
||||||
$(DOCKER_BASE)_DEPENDS += $(SUPERVISOR)
|
$(DOCKER_BASE)_DEPENDS += $(SUPERVISOR)
|
||||||
$(DOCKER_BASE)_DEPENDS += $(LIBWRAP)
|
|
||||||
$(DOCKER_BASE)_DEPENDS += $(BASH)
|
$(DOCKER_BASE)_DEPENDS += $(BASH)
|
||||||
$(DOCKER_BASE)_DEPENDS += $(SOCAT)
|
$(DOCKER_BASE)_DEPENDS += $(SOCAT)
|
||||||
|
|
||||||
|
@ -12,6 +12,5 @@ SONIC_INSTALL_DOCKER_IMAGES += $(DOCKER_SNMP_SV2)
|
|||||||
$(DOCKER_SNMP_SV2)_CONTAINER_NAME = snmp
|
$(DOCKER_SNMP_SV2)_CONTAINER_NAME = snmp
|
||||||
$(DOCKER_SNMP_SV2)_RUN_OPT += --net=host --privileged -t
|
$(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 /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
|
# 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
|
$(DOCKER_SNMP_SV2)_RUN_OPT += -v /usr/lib/python3/dist-packages/arista:/usr/lib/python3/dist-packages/arista:ro
|
||||||
|
@ -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)
|
|
1
slave.mk
1
slave.mk
@ -475,7 +475,6 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \
|
|||||||
$(PYTHON_CLICK) \
|
$(PYTHON_CLICK) \
|
||||||
$(SONIC_UTILS) \
|
$(SONIC_UTILS) \
|
||||||
$(BASH) \
|
$(BASH) \
|
||||||
$(LIBWRAP) \
|
|
||||||
$(LIBPAM_TACPLUS) \
|
$(LIBPAM_TACPLUS) \
|
||||||
$(LIBNSS_TACPLUS)) \
|
$(LIBNSS_TACPLUS)) \
|
||||||
$$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \
|
$$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \
|
||||||
|
@ -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)
|
|
||||||
|
|
@ -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;
|
|
||||||
|
|
Reference in New Issue
Block a user