ssh and snmp allow list (#1363)
- Service ACL framework for Arista platforms
This commit is contained in:
parent
e9dd064f82
commit
ecf5c8d311
@ -228,6 +228,9 @@ 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
|
||||||
|
|
||||||
|
@ -43,6 +43,8 @@ RUN apt-get update && apt-get install -y libperl5.20 libpci3 libwrap0 \
|
|||||||
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
|
||||||
|
132
dockers/docker-snmp-sv2/snmpd-config-updater
Executable file
132
dockers/docker-snmp-sv2/snmpd-config-updater
Executable file
@ -0,0 +1,132 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Daemon that listens to updates about the source IP prefixes from which snmp access
|
||||||
|
# is allowed. In case of change, it will update the snmp configuration file accordingly.
|
||||||
|
# Also, after a change, it will notify snmpd to re-read its config file (service reload).
|
||||||
|
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import redis
|
||||||
|
|
||||||
|
service="snmpd"
|
||||||
|
config_file_path="/etc/snmp"
|
||||||
|
redis_key="SNMP_ALLOW_LIST" # the redis list we listen to
|
||||||
|
subscription='__keyspace@0__:%s' % redis_key
|
||||||
|
temporization_duration = 3 # how long we wait for changes to settle (ride out a bursts of changes in redis_key)
|
||||||
|
fake_infinite = 9999 # How often we wake up when nothing is going on --get_message()'s timeout has no 'infinite' value
|
||||||
|
# after these operations we may need to revisit existing ssh connections because they removed or modified existing entries
|
||||||
|
delete_operations = ["lrem", "lpop", "rpop", "blpop", "brpop", "brpoplpush", "rpoplpush", "ltrim", "del", "lset"]
|
||||||
|
|
||||||
|
r = redis.StrictRedis(host='localhost')
|
||||||
|
p = r.pubsub()
|
||||||
|
|
||||||
|
# If redis is not up yet, this can fail, so wait for redis to be available
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
p.subscribe(subscription)
|
||||||
|
break
|
||||||
|
except redis.exceptions.ConnectionError:
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
# We could loose contact with redis at a later stage, in which case we will exit with
|
||||||
|
# return code -2 and supervisor will restart us, at which point we are back in the
|
||||||
|
# while loop above waiting for redis to be ready.
|
||||||
|
try:
|
||||||
|
|
||||||
|
# By default redis does enable events, so enable them
|
||||||
|
r.config_set("notify-keyspace-events", "KAE")
|
||||||
|
|
||||||
|
|
||||||
|
# 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).
|
||||||
|
|
||||||
|
def write_configuration_file(v):
|
||||||
|
filename="%s/%s.conf" % (config_file_path, 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 l in snmpd_config.split('\n'):
|
||||||
|
m = re.match("^(..)community (\S+)", l)
|
||||||
|
if not m:
|
||||||
|
f.write(l)
|
||||||
|
f.write("\n")
|
||||||
|
else:
|
||||||
|
if not l.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(v):
|
||||||
|
for value in v:
|
||||||
|
f.write("%s %s\n" % (this_community, value))
|
||||||
|
else:
|
||||||
|
f.write("%s\n" % this_community)
|
||||||
|
f.close()
|
||||||
|
os.rename(filename_tmp, filename)
|
||||||
|
os.system("kill -HUP $(pgrep snmpd) > /dev/null 2> /dev/null || :")
|
||||||
|
|
||||||
|
# write initial configuration
|
||||||
|
write_configuration_file(r.lrange(redis_key, 0, -1))
|
||||||
|
|
||||||
|
# listen for changes and rewrite configuration file if needed, after some temporization
|
||||||
|
#
|
||||||
|
# How those subscribed to messages look like, for reference:
|
||||||
|
# {'pattern': None, 'type': 'subscribe', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 1L}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'rpush'}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'lpush'}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'lrem'}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'lset'}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'del'}
|
||||||
|
|
||||||
|
select_timeout = fake_infinite
|
||||||
|
config_changed = False
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
m = p.get_message(timeout=select_timeout)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(-2)
|
||||||
|
# temporization: no change after 'timeout' seconds -> commit any accumulated changes
|
||||||
|
if not m and config_changed:
|
||||||
|
write_configuration_file(r.lrange(redis_key, 0, -1))
|
||||||
|
config_changed = False
|
||||||
|
select_timeout = fake_infinite
|
||||||
|
if m and m['type'] == "message":
|
||||||
|
if m['channel'] != subscription:
|
||||||
|
print "WTF: unexpected case"
|
||||||
|
continue
|
||||||
|
config_changed = True
|
||||||
|
select_timeout = temporization_duration
|
||||||
|
# some debugs for now
|
||||||
|
print "-------------------- config change: ",
|
||||||
|
if m["data"] in delete_operations:
|
||||||
|
print "DELETE"
|
||||||
|
else:
|
||||||
|
print ""
|
||||||
|
v = r.lrange(redis_key, 0, -1)
|
||||||
|
for value in v:
|
||||||
|
print value
|
||||||
|
|
||||||
|
except redis.exceptions.ConnectionError as e:
|
||||||
|
sys.exit(-2)
|
||||||
|
|
||||||
|
|
@ -11,6 +11,15 @@ 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=1
|
||||||
|
autostart=true
|
||||||
|
autorestart=unexpected
|
||||||
|
startsecs=0
|
||||||
|
stdout_logfile=syslog
|
||||||
|
stderr_logfile=syslog
|
||||||
|
|
||||||
[program:rsyslogd]
|
[program:rsyslogd]
|
||||||
command=/usr/sbin/rsyslogd -n
|
command=/usr/sbin/rsyslogd -n
|
||||||
priority=2
|
priority=2
|
||||||
|
@ -224,6 +224,18 @@ 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
|
||||||
|
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 cp src/libwrap/tcp-wrappers-7.6.q/tcpdmatch $FILESYSTEM_ROOT/usr/bin
|
||||||
|
|
||||||
## 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/
|
||||||
|
|
||||||
|
82
files/image_config/ssh/sshd-clear-denied-sessions
Executable file
82
files/image_config/ssh/sshd-clear-denied-sessions
Executable file
@ -0,0 +1,82 @@
|
|||||||
|
#!/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"])
|
||||||
|
|
133
files/image_config/ssh/sshd-config-updater
Executable file
133
files/image_config/ssh/sshd-config-updater
Executable file
@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Daemon that listens to updates from redis 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.
|
||||||
|
|
||||||
|
# Currently this code uses redis, but may have to be rewritten to use the abstracted
|
||||||
|
# apis from ./sonic-py-swsssdk/src/swsssdk/configdb.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import redis
|
||||||
|
|
||||||
|
service="sshd"
|
||||||
|
config_file_path="/etc"
|
||||||
|
redis_key="SSH_ALLOW_LIST" # the redis list we listen to
|
||||||
|
subscription='__keyspace@0__:%s' % redis_key
|
||||||
|
temporization_duration = 3 # how long we wait for changes to settle (ride out a bursts of changes in redis_key)
|
||||||
|
fake_infinite = 9999 # How often we wake up when nothing is going on --get_message()'s timeout has no 'infinite' value
|
||||||
|
# after these operations we may need to revisit existing ssh connections because they removed or modified existing entries
|
||||||
|
delete_operations = ["lrem", "lpop", "rpop", "blpop", "brpop", "brpoplpush", "rpoplpush", "ltrim", "del", "lset"]
|
||||||
|
|
||||||
|
r = redis.StrictRedis(host='localhost')
|
||||||
|
p = r.pubsub()
|
||||||
|
|
||||||
|
# If redis is not up yet, this can fail, so wait for redis to be available
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
p.subscribe(subscription)
|
||||||
|
break
|
||||||
|
except redis.exceptions.ConnectionError:
|
||||||
|
time.sleep(3)
|
||||||
|
|
||||||
|
# We could lose contact with redis at a later stage, in which case we will exit with
|
||||||
|
# return code -2 and supervisor will restart us, at which point we are back in the
|
||||||
|
# while loop above waiting for redis to be ready.
|
||||||
|
try:
|
||||||
|
|
||||||
|
# By default redis does enable events, so enable them
|
||||||
|
r.config_set("notify-keyspace-events", "KAE")
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
def write_configuration_file(v):
|
||||||
|
filename="%s/%s.allow" % (config_file_path, service)
|
||||||
|
if len(v) == 0:
|
||||||
|
if os.path.exists(filename): os.remove(filename)
|
||||||
|
return
|
||||||
|
filename_tmp = filename + ".tmp"
|
||||||
|
f=open(filename_tmp, "w")
|
||||||
|
for value in v:
|
||||||
|
f.write("%s: %s\n" % (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")
|
||||||
|
|
||||||
|
# write initial configuration
|
||||||
|
write_configuration_file(r.lrange(redis_key, 0, -1))
|
||||||
|
|
||||||
|
# listen for changes and rewrite configuration file if needed, after some temporization
|
||||||
|
#
|
||||||
|
# How those subscribed to messages look like, for reference:
|
||||||
|
# {'pattern': None, 'type': 'subscribe', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 1L}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'rpush'}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'lpush'}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'lrem'}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'lset'}
|
||||||
|
# {'pattern': None, 'type': 'message', 'channel': '__keyspace@0__:SNMP_PERMIT_LIST', 'data': 'del'}
|
||||||
|
|
||||||
|
select_timeout = fake_infinite
|
||||||
|
config_changed = False
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
m = p.get_message(timeout=select_timeout)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(-2)
|
||||||
|
# temporization: no change after 'timeout' seconds -> commit any accumulated changes
|
||||||
|
if not m and config_changed:
|
||||||
|
write_configuration_file(r.lrange(redis_key, 0, -1))
|
||||||
|
config_changed = False
|
||||||
|
select_timeout = fake_infinite
|
||||||
|
if m and m['type'] == "message":
|
||||||
|
if m['channel'] != subscription:
|
||||||
|
print "WTF: unexpected case"
|
||||||
|
continue
|
||||||
|
config_changed = True
|
||||||
|
select_timeout = temporization_duration
|
||||||
|
# some debugs for now
|
||||||
|
print "-------------------- config change: ",
|
||||||
|
if m["data"] in delete_operations:
|
||||||
|
print "DELETE"
|
||||||
|
else:
|
||||||
|
print ""
|
||||||
|
v = r.lrange(redis_key, 0, -1)
|
||||||
|
for value in v:
|
||||||
|
print value
|
||||||
|
|
||||||
|
except redis.exceptions.ConnectionError as e:
|
||||||
|
sys.exit(-2)
|
||||||
|
|
||||||
|
# redis list operations, cli cheat sheet
|
||||||
|
# -create/set
|
||||||
|
# LPUSH key value [value ...] : Prepend one or multiple values to a list
|
||||||
|
# RPUSH key value [value ...] : Append one or multiple values to a list
|
||||||
|
# LPUSHX key value : Prepend a value to a list, only if the list exists
|
||||||
|
# RPUSHX key value : Append a value to a list, only if the list exists
|
||||||
|
# LINSERT key BEFORE|AFTER pivot value : Insert an element before or after another element in a list
|
||||||
|
# LSET key index value : Set the value of an element in a list by its index
|
||||||
|
# -get
|
||||||
|
# LINDEX key index : Get an element from a list by its index
|
||||||
|
# LRANGE key start stop : Get a range of elements from a list
|
||||||
|
# LLEN key : Get the length of a list
|
||||||
|
# -remove
|
||||||
|
# LREM key count value : Remove elements from a list
|
||||||
|
# LPOP key : Remove and get the first element in a list
|
||||||
|
# RPOP key : Remove and get the last element in a list
|
||||||
|
# BLPOP key [key ...] timeout : Remove and get the first element in a list, or block until one is available
|
||||||
|
# BRPOP key [key ...] timeout : Remove and get the last element in a list, or block until one is available
|
||||||
|
# BRPOPLPUSH source destination timeout : Remove a value from a list, push it to another list and return it; or block until one is available
|
||||||
|
# RPOPLPUSH source destination : Remove the last element in a list, prepend it to another list and return it
|
||||||
|
# LTRIM key start stop : Trim a list to the specified range
|
14
files/image_config/ssh/sshd-config-updater.service
Normal file
14
files/image_config/ssh/sshd-config-updater.service
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Takes care of updates to ssh config file with respect to the SSH allow list
|
||||||
|
After=network.target auditd.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,6 +3,7 @@
|
|||||||
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)
|
||||||
|
|
||||||
ifeq ($(SONIC_CONFIG_DEBUG),y)
|
ifeq ($(SONIC_CONFIG_DEBUG),y)
|
||||||
GDB = gdb
|
GDB = gdb
|
||||||
|
10
rules/libwrap.mk
Normal file
10
rules/libwrap.mk
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# libwrap packages
|
||||||
|
|
||||||
|
LIBWRAP_VERSION = 7.6.q-25
|
||||||
|
|
||||||
|
export LIBWRAP_VERSION
|
||||||
|
|
||||||
|
LIBWRAP = libwrap0_$(LIBWRAP_VERSION)_amd64.deb
|
||||||
|
$(LIBWRAP)_SRC_PATH = $(SRC_PATH)/libwrap
|
||||||
|
SONIC_MAKE_DEBS += $(LIBWRAP)
|
||||||
|
|
1
slave.mk
1
slave.mk
@ -414,6 +414,7 @@ $(addprefix $(TARGET_PATH)/, $(SONIC_INSTALLERS)) : $(TARGET_PATH)/% : \
|
|||||||
$(IXGBE_DRIVER) \
|
$(IXGBE_DRIVER) \
|
||||||
$(SONIC_DEVICE_DATA) \
|
$(SONIC_DEVICE_DATA) \
|
||||||
$(SONIC_UTILS) \
|
$(SONIC_UTILS) \
|
||||||
|
$(LIBWRAP) \
|
||||||
$(LIBPAM_TACPLUS) \
|
$(LIBPAM_TACPLUS) \
|
||||||
$(LIBNSS_TACPLUS)) \
|
$(LIBNSS_TACPLUS)) \
|
||||||
$$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \
|
$$(addprefix $(TARGET_PATH)/,$$($$*_DOCKERS)) \
|
||||||
|
23
src/libwrap/Makefile
Normal file
23
src/libwrap/Makefile
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
.ONESHELL:
|
||||||
|
SHELL = /bin/bash
|
||||||
|
.SHELLFLAGS += -e
|
||||||
|
|
||||||
|
MAIN_TARGET = libwrap0_$(LIBWRAP_VERSION)_amd64.deb
|
||||||
|
|
||||||
|
$(addprefix $(DEST)/, $(MAIN_TARGET)): $(DEST)/% :
|
||||||
|
# Remove any stale files
|
||||||
|
rm -rf ./tcp-wrappers-7.6.q*
|
||||||
|
|
||||||
|
# Get source package
|
||||||
|
apt-get source libwrap0
|
||||||
|
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 $* $(DEST)/
|
||||||
|
|
306
src/libwrap/tcp_wrappers-7.6-allowlist.patch
Normal file
306
src/libwrap/tcp_wrappers-7.6-allowlist.patch
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
--- 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