134 lines
6.3 KiB
Python
Executable File
134 lines
6.3 KiB
Python
Executable File
#!/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
|