added user_cull

This commit is contained in:
brent s 2019-01-18 14:39:45 -05:00
parent 262d10f55d
commit 6cea3c012e

122
sys/user_cull.py Executable file
View File

@ -0,0 +1,122 @@
#!/usr/bin/env python3

# Because:
# - SSH timeout doesn't work with byobu/screen/tmux
# - SSH timeout can be overridden client-side
# - $TMOUT can be overridden user-side
# we need to actually kill the sshd process attached to the SSH session.

import datetime
import os
import psutil
import subprocess

# in seconds. 5 minutes = 300 seconds.
# if "auto", we'll try checking $TMOUT in the system bashrc and sshd_config, in that order.
timeout = 'auto'
# only apply to ssh connections instead of ssh + local.
# THIS WILL KILL SCREEN/TMUX CONNECTIONS. USE WITH CAUTION.
only_ssh = True
# send a closing message.
goodbye = True
# the message to send to the user if goodbye == True.
# can use the following for substitution:
# pid - The PID if the user's login process.
# terminal - The terminal they're logged in on.
# loginlength - How long they've been logged in (in minutes).
# logintime - When they logged in.
# timeout - The allowed length of time for inactivity until a timeout.
goodbye_mesg = ('You have been logged in for {loginlength} seconds (since {logintime}) on '
'{terminal} ({pid}).\n'
'However, as per security policy, you have exceeded the allowed idle timeout ({timeout}).\n'
'As such, your session will now be terminated. Please feel free to reconnect.')
# exclude these usernames
exclude_users = []


# Get the SSHD PIDs.
ssh_pids = [p for p in psutil.process_iter() if p.name() == 'sshd']
# If the timeout is set to auto, try to find it.
if timeout == 'auto':
import re
#tmout_re = re.compile('^\s*#*(export\s*)?TMOUT=([0-9]+).*$')
tmout_re = re.compile('^\s*(export\s*)?TMOUT=([0-9]+).*$')
# We don't bother with factoring in ClientAliveCountMax.
# sshd_re = re.compile('^\s*#*ClientAliveCountMax\s+([0-9+]).*$')
sshd_re = re.compile('^\s*ClientAliveInterval\s+([0-9+]).*$')
for f in ('/etc/bashrc', '/etc/bash.bashrc'):
if not os.path.isfile(f):
continue
with open(f, 'r') as fh:
conf = f.read()
for line in conf.splitlines():
if tmout_re.search(line):
try:
timeout = int(tmout_re.sub('\g<2>', line))
break
except ValueError:
continue
if not isinstance(timeout, int): # keep going; check sshd_config
with open('/etc/ssh/sshd_config', 'r') as f:
conf = f.read()
for line in conf.splitlines():
if sshd_re.search(line):
try:
timeout = int(tmout_re.sub('\g<1>', line))
break
except ValueError:
continue
# Finally, set a default. 5 minutes is sensible.
timeout = 300


def kill_user(user):
pass

def get_idle(user):
idle_time = None
pty = user.terminal
for sssn in subprocess.run(['who', '-u'], stdout = subprocess.PIPE).stdout.decode('utf-8').splitlines():
session = sssn.split()
# This is probably overkill, but.
if not all((
(session[0] != user.name),
(session[1] != user.terminal),
(session[5] != user.pid))):
continue
# https://unix.stackexchange.com/a/332704/284004
last_used = datetime.datetime.fromtimestamp(os.stat('/dev/{0}'.format(user.terminal)).st_atime)
idle_time = datetime.datetime.utcnow() - last_used
break
return(idle_time)


for user in psutil.users():
if user.name in exclude_users:
continue
login_pid = user.pid
login_length = (datetime.datetime.utcnow() - datetime.datetime.fromtimestamp(user.started))
if login_length.total_seconds() < timeout:
continue # they haven't even been logged in for long enough yet.
idle_time = get_idle(user)
if idle_time.total_seconds() >= timeout:
fmtd_goodbye = goodbye_mesg.format({'pid': user.pid,
'terminal': user.terminal,
'loginlength': login_length,
'logintime': datetime.datetime.fromtimestamp(user.started),
'timeout': timeout})
if only_ssh:
if user.pid in ssh_pids:
if goodbye:
subprocess.run(['write',
user.name,
user.terminal],
input = fmtd_goodbye.encode('utf-8'))
psutil.Process(user.pid).terminate()
else:
if goodbye:
subprocess.run(['write',
user.name,
user.terminal],
input = fmtd_goodbye.encode('utf-8'))
psutil.Process(user.pid).terminate()