Skip to content

Instantly share code, notes, and snippets.

@wileyj
Last active December 14, 2017 22:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wileyj/398dd4a503dc8692683b3be5a43c22c0 to your computer and use it in GitHub Desktop.
Save wileyj/398dd4a503dc8692683b3be5a43c22c0 to your computer and use it in GitHub Desktop.
needs some error checking etc. as-is
#!/usr/bin/env python
import sys
import os
import argparse
import re
import errno
import stat
import pwd
import grp
# no longer needed since we hardcoded the mount cmd for now
# import ctypes
from shutil import copyfile, copymode
from subprocess import Popen, PIPE
abspath = os.path.abspath(__file__)
dname = os.path.dirname(abspath)
os.chdir(dname)
cwd = os.getcwd()
libs = {}
fstab_lines = []
jail_root = "/export/jail"
group = "jailed"
user = "rzshuser"
uid = 1501
gid = 1501
homedir = "/home/" + user
chroot_homedir = jail_root + "/home/" + user
sshdir = homedir + "/.ssh"
# keyfile = sshdir + "/authorized_keys"
empty_files = [
"/etc/zprofile",
"/etc/zlogout",
"/etc/zshrc"
]
create_dir_list = [
homedir,
sshdir,
"/lib64",
"/usr/share/terminfo/x",
"/usr/lib64",
"/dev/pts",
"/proc",
"/sys",
"/etc",
"/etc/security",
"/tmp"
]
copy_file_list = [
"/etc/nsswitch.conf",
"/etc/hosts",
"/etc/passwd",
"/etc/group",
"/etc/resolv.conf",
"/etc/system-release"
]
copy_dir_list = [
{
"source": "/lib64/libnss*",
"dest": jail_root + "/lib64/"
}, {
"source": "/usr/lib64/libnss*",
"dest": jail_root + "/usr/lib64/"
}, {
"source": "/etc/security/*",
"dest": jail_root + "/etc/security/"
}, {
"source": "/usr/share/terminfo/x/*",
"dest": jail_root + "/usr/share/terminfo/x/"
}, {
"source": "/usr/lib64/zsh",
"dest": jail_root + "/usr/lib64/"
}, {
"source": "/usr/lib64/ld-linux*",
"dest": jail_root + "/lib64/"
}
]
device_list = [
{
'name': '/dev/null',
'type': stat.S_IFCHR,
'mode': 0o666,
'major': 1,
'minor': 3
}, {
'name': '/dev/random',
'type': stat.S_IFCHR,
'mode': 0o666,
'major': 1,
'minor': 8
}, {
'name': '/dev/tty',
'type': stat.S_IFCHR,
'mode': 0o666,
'major': 1,
'minor': 8
}, {
'name': '/dev/urandom',
'type': stat.S_IFCHR,
'mode': 0o666,
'major': 1,
'minor': 9
}
]
mounts = [
{
'source': 'proc',
'dest': '/export/jail/proc',
'fstype': 'proc',
'fsoptions': 'defaults,hidepid=2',
'freq': 0,
'passno': 0
}, {
'source': 'sysfs',
'dest': '/export/jail/sys',
'fstype': 'sysfs',
'fsoptions': 'defaults,hidepid=2',
'freq': 0,
'passno': 0
}, {
'source': 'devpts',
'dest': '/export/jail/dev/pts',
'fstype': 'devpts',
'fsoptions': 'seclabel,gid=5,mode=620,ptmxmode=000',
'freq': 0,
'passno': 0
}, {
'source': '/dev',
'dest': '/export/jail/dev',
'fstype': 'defaults',
'fsoptions': 'rw,bind',
'freq': 0,
'passno': 0
}
]
packages = [
{
'binary': '/usr/bin/host',
'package': 'bind-utils'
}, {
'binary': '/bin/ping',
'package': 'iputils'
}, {
'binary': '/bin/zsh',
'package': 'zsh'
}, {
'binary': '/bin/traceroute',
'package': 'traceroute'
}, {
'binary': '/usr/bin/dig',
'package': 'bind-utils'
}, {
'binary': '/usr/bin/nslookup',
'package': 'bind-utils'
}, {
'binary': '/usr/bin/ssh',
'package': 'openssh-clients'
}, {
'binary': '/bin/nc',
'package': 'nmap-ncat'
}, {
'binary': '/sbin/nologin',
'package': 'util-linux'
}, {
'binary': '/bin/ssh-add',
'package': 'openssh-clients'
}, {
'binary': '/bin/ssh-agent',
'package': 'openssh-clients'
}, {
'binary': '/bin/whoami',
'package': 'coreutils'
}
]
pam_sshd_lines = [
"session required pam_chroot.so",
"session optional pam_motd.so motd=/etc/motd"
]
pam_sshd_file = "/etc/pam.d/sshd"
fstab_file = "/etc/fstab"
zshenv_file = "/etc/zshenv"
zshenv = """
#
# /etc/zshenv is sourced on all invocations of the
# shell, unless the -f option is set. It should
# contain commands to set the command search path,
# plus other important environment variables.
# .zshenv should not contain commands that produce
# output or assume the shell is attached to a tty.
#
export PATH=/usr/bin:/bin
alias bindkey=""
alias compcall=""
alias compctl=""
alias compsys=""
alias source=""
alias vared=""
alias zle=""
alias bg=""
disable compgroups
disable compquote
disable comptags
disable comptry
disable compvalues
disable pwd
disable alias
disable autoload
disable break
disable builtin
disable command
disable comparguments
disable compcall
disable compctl
disable compdescribe
disable continue
disable declare
disable dirs
disable disown
disable echo
disable echotc
disable echoti
disable emulate
disable enable
disable eval
disable exec
disable export
disable false
disable float
disable functions
disable getln
disable getopts
disable hash
disable integer
disable let
disable limit
disable local
disable log
disable noglob
disable popd
disable print
disable pushd
disable pushln
disable read
disable readonly
disable rehash
disable sched
disable set
disable setopt
disable shift
disable source
disable suspend
disable test
disable times
disable trap
disable true
disable ttyctl
disable type
disable typeset
disable ulimit
disable umask
disable unalias
disable unfunction
disable unhash
disable unlimit
disable unset
disable unsetopt
disable vared
disable whence
disable where
disable which
disable zcompile
disable zformat
disable zle
disable zmodload
disable zparseopts
disable zregexparse
disable zstyle
"""
def ldd(binary):
print "*** Running ldd on %s" % (binary)
cmd = "/usr/bin/ldd " + binary
return get_libs(run_cmd(cmd))
def mount(source, target, fs, options=''):
print "\n*** Mounting (%s -> %s)" % (source, target)
if source == "/dev" or source == "/sys":
print "\tfound /dev mount....running this as syscall"
mount_cmd = "mount --bind " + source + " " + target
run_cmd(mount_cmd)
else:
print "Mounting: %s %s %s %s" % (source, target, fs, options)
mount_cmd = "mount -t %s %s -o %s %s" % (fs, source, options, target)
run_cmd(mount_cmd)
# this isn't working since it can't handle some of the options we need.
# need to investigate later if it's possible to fix
# ret = ctypes.CDLL('libc.so.6', use_errno=True).mount(source, target, fs, 4096, options)
# if ret < 0:
# errno = ctypes.get_errno()
# raise RuntimeError("Error mounting {} ({}) on {} with options '{}': {}".
# format(source, fs, target, options, os.strerror(errno)))
#
def create_symlink(source, target):
print "\n*** Creating SymLink (%s -> %s)" % (source, target)
# return True
try:
os.symlink(source, target)
return True
except OSError as exc:
print "\t - Failed"
if exc.errno != errno.EEXIST:
raise
pass
return False
def write_file(filename, content):
print "\n*** Writing File: %s" % (filename)
file = open(filename, "w")
file.write(content)
file.close()
return True
def create_dir(dir):
print "\n*** Creating Directory %s" % (dir)
# return True
try:
os.makedirs(dir)
return True
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
pass
return False
def create_dev(device, dev_type, dev_mode, major, minor):
print "\n*** Creating Device %s" % (device)
print "\t - type: %s" % (dev_type)
print "\t - mode: %i" % (dev_mode)
print "\t - major: %i" % (major)
print "\t - minor: %i" % (minor)
try:
os.mknod(device, dev_type, os.makedev(major, minor))
os.chmod(device, dev_mode)
return True
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
pass
return False
def copy_lib(name, source, target):
print "\tCopy %s (%s -> %s)" % (name, source, target)
# return True
if target == "/export/jail/bin/ssh-agent" and os.path.exists("/export/jail/bin/ssh-agent"):
print "\t * Matched ssh-agent, but ssh-agent already running. Skipping"
else:
try:
copyfile(source, target)
copymode(source, target)
return True
except OSError as exc:
if exc.errno != errno.EEXIST:
raise
pass
return False
def get_libs(p):
stdout = p.communicate()
lib_list = str(stdout[0]).split("\n")[:-1]
if len(lib_list) > 0:
for item in lib_list:
if re.search("linux-vdso.so", item.split(" ")[0]) is None:
if item.split()[0][0] != "/":
source = item.split(" ")[0].strip()
target = os.path.realpath(item.split(" ")[2].strip())
else:
source = item.split(" ")[0].strip()
target = os.path.realpath(item.split(" ")[0].strip())
libs[source] = {
'name': source,
'link': os.path.islink(source),
'link_source': os.path.realpath(source),
'source': source,
'target': target,
'base_path_source': os.path.dirname(source),
'base_path_target': os.path.dirname(target)
}
return True
else:
return False
def remove_suid():
print "\n*** Removing SUID bits"
cmd = 'find /export/jail ! -path "*/proc*" -perm -4000 -type f'
stdout = run_cmd(cmd).communicate()
suid_list = str(stdout[0]).split("\n")[:-1]
for item in suid_list:
print "Remove SUID from: %s" % (item)
run_cmd("chmod u-s " + item)
return True
def run_cmd(cmd):
print "*** Running Command: %s" % (cmd)
p = Popen(cmd, shell=True, stdout=PIPE)
p.wait()
return p
def yum_install(package):
print "\t Installing Package: %s" % (package)
p = Popen("yum install -y " + package, shell=True, stdout=PIPE)
return p.returncode
def check_fstab(lines):
print "\n*** Checking /etc/fstab"
f = open(fstab_file, 'r')
w = open(fstab_file, 'a')
g = f.readlines()
f.close()
for item in lines:
matched = 0
token = item.split()
for line in g:
if re.search("^" + token[0] + "\s+" + token[1], line) is not None:
print "Matched: %s" % (token[0])
matched = 1
break
if matched != 1:
print "Writing line to fstab: %s" % (item)
w.write(item + "\n")
w.close()
def check_pamd_sshd(lines):
print "\n*** Checking file %s" % (pam_sshd_file)
f = open(pam_sshd_file, 'r')
w = open(pam_sshd_file, 'a')
g = f.readlines()
f.close()
for item in lines:
matched = 0
# print "item: %s" % (item)
token = item.split()
# print "\t len: %i" % (len(token))
# print "token0: %s" % (token[0])
for line in g:
# print "line: %s" % (line.strip())
if re.search("^" + token[0] + "\s+" + token[1] + "\s+" + token[2], line) is not None:
print "\tMatched: %s" % (line.strip())
matched = 1
break
if matched != 1:
print "\t - writing pam.d ssh line: %s" % (item)
w.write(item + "\n")
w.close()
def touch(fname, times=None):
print "\n*** Touching File: %s" % (fname)
with open(fname, 'a'):
os.utime(fname, times)
def check_user():
print "\n*** Checking for user: %s" % (user)
try:
print "\t - Found User"
pwd.getpwnam(user)
except KeyError:
print "\t %s Missing" % (user)
print "\t - Creating user: %s" % (user)
# create user manually.....lame
cmd = "useradd -d " + homedir + " -M -N -o -g " + group + " -u " + str(uid) + " -c 'bastion " + user + " account' -s /sbin/nologin " + user
run_cmd(cmd)
return True
def check_group():
print "\n*** Checking group: %s" % (group)
try:
print "\tFound group"
grp.getgrnam(group)
pass
except KeyError:
print "\t %s Missing" % (group)
print "\t - Creating group: %s" % (group)
# create group manually.....lame
cmd = "groupadd -g " + gid + " " + group
run_cmd(cmd)
return True
def check_rzshuser_files():
check_user()
check_group()
print "\n*** Checking user(%s) files" % (user)
if not os.path.exists(homedir):
print "\tHomedir Missing"
print "\t - Creating user homedir " + homedir
if not create_dir(homedir):
print "\t - Failed to create user homedir: %s" % (homedir)
# if not os.path.exists(sshdir):
# print " - Creating user sshdir " + sshdir
# if not create_dir(sshdir):
# print "Failed to create user sshdir: %s" % (sshdir)
# if not os.path.exists(keyfile):
# print "Creating user authorized_keys file " + keyfile
# touch(keyfile)
# check file perms
st = os.stat(homedir)
if oct(st.st_mode)[-3:] is not 750:
print "\t - Setting %s perm to %s" % (homedir, "0o770")
os.chmod(homedir, 0o770)
os.chown(homedir, uid, gid)
# else:
# print "homedir perm: %s" % (oct(st.st_mode)[-3:])
# st = os.stat(sshdir)
# if oct(st.st_mode)[-3:] is not 700:
# print "setting %s perm to %s" % (sshdir, "0o700")
# os.chmod(sshdir, 0o700)
# os.chown(sshdir, uid, gid)
# else:
# print "sshdir perm: %s" % (oct(st.st_mode)[-3:])
# st = os.stat(keyfile)
# if oct(st.st_mode)[-3:] is not 640:
# print "setting %s perm to %s" % (keyfile, "0o640")
# os.chmod(keyfile, 0o640)
# os.chown(keyfile, uid, gid)
# else:
# print "keyfile perm: %s" % (oct(st.st_mode)[-3:])
if os.path.exists(chroot_homedir):
print "Found existing jail homedir: %s" % (chroot_homedir)
st = os.stat(chroot_homedir)
if oct(st.st_mode)[-3:] is not 770:
print " chmod 770 on %s" % (chroot_homedir)
os.chmod(chroot_homedir, 0o770)
print " chown %i %i on %s" % (uid, gid, chroot_homedir)
os.chown(chroot_homedir, uid, gid)
return True
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(
'-v',
dest='verbose',
nargs='?',
help="Verbosity"
)
parser.add_argument(
'--binary',
nargs='?',
metavar='',
default="",
help="Binary",
)
args = parser.parse_args()
for item in create_dir_list:
if not os.path.exists(jail_root + item):
if not create_dir(jail_root + item):
print "Exiting. Failed to create source dir: %s" % (jail_root + item)
sys.exit(2)
if item is "/tmp":
print "Found tmp dir: %s" % (item)
run_cmd("chmod a+w " + jail_root + item)
for item in copy_file_list:
if not os.path.exists(jail_root + item):
copy_lib(
os.path.basename(item),
item,
jail_root + item
)
for item in copy_dir_list:
if os.path.exists(item['dest']):
run_cmd("cp -a " + item['source'] + " " + item['dest'])
else:
print "Missing Destination path: %s" % (item['dest'])
for fsmount in mounts:
if not os.path.ismount(fsmount['dest']):
print "\t - %s is not mounted: %s" % (mount, fsmount['dest'])
mount(fsmount['source'], fsmount['dest'], fsmount['fstype'], fsmount['fsoptions'])
fstab_lines.append(fsmount['source'] + "\t" + fsmount['dest'] + "\t" + fsmount['fstype'] + "\t" + fsmount['fsoptions'] + "\t0 0")
for device in device_list:
dev_dir = os.path.dirname(jail_root + device['name'])
if not os.path.exists(dev_dir):
print "Source Missing: %s" % (dev_dir)
if not create_dir(dev_dir):
print "Failed to create source dir: %s" % (dev_dir)
sys.exit(2)
create_dev(jail_root + device['name'], device['type'], device['mode'], device['major'], device['minor'])
if os.path.exists("/export/jail" + sshdir):
if os.path.exists("/export/jail" + sshdir + "/known_hosts") and not os.path.islink("/export/jail" + sshdir + "/known_hosts"):
print "Found %s....Removingit" % ("/export/jail/" + sshdir + "/known_hosts")
os.remove("/export/jail/" + sshdir + "/known_hosts")
create_symlink("/export/jail/dev/null", "/export/jail" + sshdir + "/known_hosts")
else:
create_symlink("/export/jail/dev/null", "/export/jail" + sshdir + "/known_hosts")
if args.binary:
ldd(args.binary)
else:
print "*** Checking on Packages/Binaries"
for item in packages:
print "Checking if %s exists" % (item['binary'])
file_name = os.path.basename(item['binary'])
file_source = item['binary']
file_target = jail_root + item['binary']
if file_name == "zsh":
file_target = file_target.replace("zsh", "rzsh")
if not os.path.exists(file_source):
print "Installing Package: %s" % (item['package'])
run_cmd("yum install -y " + item['package'])
if os.path.exists(file_source) and not os.path.exists(file_target):
ldd(item['binary'])
if not os.path.exists(jail_root + os.path.dirname(item['binary'])):
print "Source Missing: %s" % (jail_root + os.path.dirname(item['binary']))
if not create_dir(jail_root + os.path.dirname(item['binary'])):
print "Failed to create binary dir: %s" % (jail_root + os.path.dirname(item['binary']))
sys.exit(1)
copy_lib(
file_name,
file_source,
file_target
)
if re.search("ping", item['binary']):
os.chmod(jail_root + item['binary'], 0o04755)
if len(libs) > 0:
print "*** Total Number of libraries to copy: %i" % (len(libs))
for lib in libs:
base_path_source = jail_root + libs[lib]['base_path_source']
base_path_target = jail_root + libs[lib]['base_path_target']
if not os.path.exists(base_path_source):
print "- Source Missing: %s" % (base_path_source)
if not create_dir(base_path_source):
print "Failed to create source dir: %s" % (base_path_source)
sys.exit(1)
if not os.path.exists(base_path_target):
print "- Target Missing: %s" % (base_path_target)
if not create_dir(base_path_target):
print "Failed to create target dir: %s" % (base_path_target)
sys.exit(1)
if libs[lib]['link']:
copy_lib(
os.path.basename(libs[lib]['link_source']),
os.path.dirname(libs[lib]['source']) + "/" + os.path.basename(libs[lib]['link_source']),
jail_root + os.path.dirname(libs[lib]['source']) + "/" + os.path.basename(libs[lib]['link_source'])
)
create_symlink(
jail_root + os.path.dirname(libs[lib]['source']) + "/" + os.path.basename(libs[lib]['link_source']),
base_path_target + "/" + os.path.basename(libs[lib]['name'])
)
else:
copy_lib(
libs[lib]['name'],
libs[lib]['base_path_target'] + "/" + libs[lib]['name'],
base_path_target + "/" + libs[lib]['name']
)
remove_suid()
for filename in empty_files:
content = ""
write_file(jail_root + filename, content)
write_file(jail_root + zshenv_file, zshenv)
check_pamd_sshd(pam_sshd_lines)
check_rzshuser_files()
if (len(fstab_lines) > 0):
check_fstab(fstab_lines)
run_cmd("mount -a")
# need a check around this command: it'll fail if run more than once
if not os.path.exists("/export/jail/tmp/ssh-agent"):
run_cmd("/export/jail/bin/ssh-agent -a /export/jail/tmp/ssh-agent")
write_file("/etc/sysconfig/chroot_setup", "1")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment