Signed-off-by: Georg <georg@lysergic.dev>
This commit is contained in:
Georg Pfuetzenreuter 2021-08-14 10:02:44 +02:00
commit c27753da86
Signed by: Georg
GPG Key ID: 1DAF57F49F8E8F22
11 changed files with 436 additions and 0 deletions

1
README.md Normal file
View File

@ -0,0 +1 @@
Hosts configurations related to our POC shell service.

View File

@ -0,0 +1,13 @@
#!/bin/sh
# Original by https://github.com/sleeepyjack/dockersh
# Modified by georg@lysergic.dev
pip3 install --upgrade -r requirements.txt
if [ -z "$1" ]; then
activate-global-python-argcomplete
else
activate-global-python-argcomplete --dest=$1
fi
cp dockersh /opt/dockersh/bin/
chmod +x /opt/dockersh/bin/dockersh
cp -n dockersh.ini /opt/dockersh/etc/

29
etc/dockersh.ini Normal file
View File

@ -0,0 +1,29 @@
[ADMIN]
command = admin
shell = /bin/bash
names =
cranberry-adm
mogad0n-adm
maintenance = off
maintenance_scp = on
maintenance_text = This Maschine is in Maintanence Mode. However, you can copy files with `scp`, `rsync`, `sftp` or list files with `ls` without connecting to the maschine. I.e.
ssh ${HOSTNAME} ls -la
[DEFAULT]
image = ubuntu_user
suffix = _${USER}
homedir = /home/${USER}
shell = /bin/bash
greeting = Ready.
[test01]
image = test01
[test02]
image = test02
[test03]
image = test03
[cranberry-test]
image = cranberry-test

23
etc/motd Normal file
View File

@ -0,0 +1,23 @@
#################################################################
## ##
## --- https://liberta.casa --- ##
## ##
## - This is an EXPERIMENTAL, Proof Of Concept, environment. ##
## - You are being offered an isolated container which ##
## you are free to experiment in. ##
## - Sudo is not configured by default, but you may ##
## use root password ` freedom `. ##
## - Containers stay running/online for a while ##
## after `exit`ing. ##
## - The memory shown in common CLI tools is NOT accurate - ##
## if you exceed 512MB, processes may get killed. ##
## - At this stage, shells may be reset at any time. ##
## - If you encounter issues, please let us know. ##
## ##
## For your sanity: ##
## - Do NOT consider this a secure environment. ##
## - Do NOT consider this a reliable environment. ##
## - Be considerate, don't waste other users resources. ##
## - Help: Type ` help.sh ` or join #help on irc.liberta.casa ##
## ##
#################################################################

2
etc/sshd/sshd-banner Normal file
View File

@ -0,0 +1,2 @@
You are connecting to the LibertaCasa public shell infrastructure.
If you do not have an account yet, please request one in #libcasa.info on irc.liberta.casa (Webchat: https://liberta.casa/gamja).

4
lcpubsh/bin/adduser.sh Normal file
View File

@ -0,0 +1,4 @@
#!/bin/sh
# georg@lysergic.dev
# The filename is flawed, however changing it would likely break scripts in three other places.
echo "$1:$2" | chpasswd

48
lcpubsh/bin/generate.sh Normal file
View File

@ -0,0 +1,48 @@
#!/bin/sh
# georg@lysergic.dev
set -e
echo "Shell generation invoked." | nc -N 127.0.0.2 2424
if [ ! "$#" -eq 0 ]; then
user="$(echo "$1" |tr '[:upper:]' '[:lower:]')"
case "$2" in
"archlinux")
os="archlinux"
image="lc-archlinux-userbase-v2:sh0"
;;
"ubuntu")
os="ubuntu"
image="lcbase_ubuntu_14082021_2:sh0"
;;
*)
echo "Choose between archlinux or ubuntu"
exit 1
;;
esac
fingerprint_ecdsa="$(ssh-keygen -lf /etc/ssh/ssh_host_ecdsa_key.pub)"
if id "$1" &>/dev/null; then
echo "Aborted. Username is already taken."
echo "Aborted: $user is already taken." | nc -N 127.0.0.2 2424
else
echo "Hang on ..."
echo "Creating $user locally." | nc -N 127.0.0.2 2424
sudo useradd -mUs /opt/lcpubsh/bin/pubsh -G docker $user
pass=$(shuf -n2 /usr/share/dict/words | tr -d '\n')
echo "Appending to config." | nc -N 127.0.0.2 2424
echo "" >> /etc/dockersh.ini
echo "[$user]" >> /etc/dockersh.ini
echo "image = $user" >> /etc/dockersh.ini
echo "Forking Docker base image ($image)." | nc -N 127.0.0.2 2424
/opt/lcpubsh/bin/make_lc_user_image.sh $user $image | nc -N 127.0.0.2 2424
echo "Setting password." | nc -N 127.0.0.2 2424
sudo /opt/adduser.sh $user $pass
echo "@$user ssh -p 2222 $user@sh.lib.casa" | nc -N 127.0.0.2 2424
echo "@$user $fingerprint_ecdsa" | nc -N 127.0.0.2 2424
echo "@$user $pass" | nc -N 127.0.0.2 2424
echo "#universe $pass" | nc -N 127.0.0.2 2424
echo "Done." | nc -N 127.0.0.2 2424
echo "OK. Details sent to user and/or admins."
fi
else
echo "No argument supplied."
fi

View File

@ -0,0 +1,12 @@
#!/bin/bash
# Original by https://github.com/sleeepyjack/dockersh
# Modified by georg@lysergic.dev
if [ -z "$1" -o -z "$2" ]; then
echo "./make_user_image.sh [name] [source-image]"; exit 100
fi
sed -i "1s/.*/FROM $2/" /opt/dockersh/dockersh-git/image_template/Dockerfile
cd /opt/dockersh/dockersh-git/image_template
docker build -t $1:sh0 .

274
lcpubsh/bin/pubsh Executable file
View File

@ -0,0 +1,274 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
# Original by https://github.com/sleeepyjack/dockersh
# Modified by georg@lysergic.dev
# POC / IN DEVELOPMENT
# Do NOT use this in insecure environments
import os
os.environ['TERM'] = 'xterm' # removes warning on non-tty commands
import argparse
#import argcomplete
#from argcomplete.completers import ChoicesCompleter
from configparser import ConfigParser, ExtendedInterpolation
import docker
import random
import string
import sys
from pwd import getpwnam
import socket
import os
import getpass
prog = 'dockersh'
version = prog + " v1.0"
config_file = "/etc/dockersh.ini"
user = getpass.getuser()
hostname = socket.gethostname()
cli = docker.APIClient()
def containers(image_filter='', container_filter='', sort_by='Created', all=True):
cs = cli.containers(all=all, filters={'label': "user="+user})
cs.sort(key=lambda c: c[sort_by])
cs = [c for c in cs if str(c['Image']+':sh0').startswith(image_filter)]
cs = [c for c in cs if c['Names'][0][1:].startswith(container_filter)]
return cs
def random_string(length):
def random_char():
return random.choice(string.ascii_uppercase + string.digits)
return ''.join(random_char() for _ in range(length))
def strip(s, suffix=''):
for c in ['/', ':', '.', ' ']: #QUESTION does this suffice?
s = s.replace(c, '')
if s.endswith(suffix):
s = s[:len(s)-len(suffix)]
return s
def pull(image):
if not image in image_names:
s = image.split(':')
if len(s) > 1:
cli.pull(s[0], s[1])
else:
cli.pull(s[0])
def image_split(s):
sp = s.split(':')
if len(sp) == 1:
return sp[0], 'sh0'
else:
return sp[0], sp[1]
def selection_menu(choices):
if len(choices) == 1:
return 0
print("There are multiple matching containers running:")
for j, c in enumerate(choices):
print("[" + str(j+1) + "]\t" + c)
inp = input("select [1]: ")
if inp == "":
i = 0
else:
i = int(inp) - 1
assert(0 <= i < len(choices))
return i
#if __name__ == "__main__":
def parse_args():
parser = argparse.ArgumentParser(prog=prog)
parser.add_argument('--version',
action='version',
version=version)
parser.add_argument('-i', '--image',
dest='image',
help="base image to be used",
default="") #.completer = ChoicesCompleter(tuple(images))
parser.add_argument('-n', '--name',
dest='name',
help="container name",
default="") #.completer = ChoicesCompleter(tuple(containers))
parser.add_argument('-t', '--temporary',
dest='temp',
action='store_true',
help="execute in temporary container",
default=False)
parser.add_argument('-c', '--command',
dest='cmd',
help="pass command to bash in container",
default="")
parser.add_argument('--home',
dest='home',
help="user home directory",
default=ini['homedir'])
#argcomplete.autocomplete(parser) #TODO make autocompletion work
args = parser.parse_args()
args.suffix = ini['suffix']
args.greeting = ini['greeting']
args.ini = ini
return args
# load ini
cfg = ConfigParser({"USER": user, "HOSTNAME": hostname}, interpolation=ExtendedInterpolation())
cfg.read(config_file, encoding="utf-8")
if os.getenv("USER") and user != os.getenv('USER') and user in cfg["ADMIN"]["names"].splitlines():
user = os.getenv('USER')
# reread config
cfg = ConfigParser({"USER": user, "HOSTNAME": hostname}, interpolation=ExtendedInterpolation())
cfg.read(config_file, encoding="utf-8")
admin_cmd = "admin"
admin_shell = "/bin/bash"
if cfg.has_section("ADMIN") and "command" in cfg["ADMIN"]:
admin_cmd = cfg["ADMIN"]["command"]
if "admin_shell" in cfg["ADMIN"]:
admin_shell = cfg["ADMIN"]["admin_shell"]
ini = cfg[user] if cfg.has_section(user) else cfg['DEFAULT']
#if __name__ == "__main__":
args = parse_args()
if args.cmd == admin_cmd:
print("Trying to login into host: "+user)
if not sys.stdout.isatty():
print()
print("admin mode is only possible using pseudo tty-allocation.")
print("Try login using:")
print("ssh -t ...")
sys.exit(0)
os.system("sudo -u "+user+" sudo "+admin_shell)
sys.exit(0)
if cfg.has_section("ADMIN") and "maintenance" in cfg["ADMIN"] and cfg["ADMIN"]["maintenance"] == "on" and (not "maintenance_scp" in cfg["ADMIN"] or cfg["ADMIN"]["maintenance_scp"] != "on"):
if "maintenance_text" in cfg["ADMIN"]:
print(cfg["ADMIN"]["maintenance_text"])
else:
print("This Maschine is in Maintanence Mode.")
sys.exit(0)
is_scp_cmd = False
if args.cmd:
if os.path.basename(args.cmd).startswith(("scp","rsync --server","sftp-server","ls","*")):
is_scp_cmd = True
if args.cmd == "envir":
print(os.environ)
name_passed = (args.name != "")
image_passed = (args.image != "")
if not is_scp_cmd and cfg.has_section("ADMIN") and "maintenance" in cfg["ADMIN"] and cfg["ADMIN"]["maintenance"] == "on":
if "maintenance_text" in cfg["ADMIN"]:
print(cfg["ADMIN"]["maintenance_text"])
else:
print("This Maschine is in Maintanence Mode. However, you can copy files with scp, rsync, sftp or list files with ls without connecting to the maschine.")
sys.exit(0)
if args.temp:
if not image_passed:
args.image = args.ini['image']
args.image_base, args.image_tag = image_split(args.image)
args.image = args.image_base + ':' + args.image_tag
args.name = strip(args.image) + '_tmp' + random_string(4)
else:
if name_passed:
args.name = strip(args.name, args.suffix)
filtered_con = containers(image_filter=args.image, container_filter=args.name)
if len(filtered_con) > 0:
con_names = [c['Names'][0][1:] for c in filtered_con]
i = selection_menu(con_names)
args.name = strip(con_names[i], args.suffix)
else:
if not image_passed:
args.image = args.ini['image']
args.image_base, args.image_tag = image_split(args.image)
args.image = args.image_base + ':' + args.image_tag
if not name_passed:
args.name = strip(args.image)
if len(containers(container_filter=args.name)) != 0:
print("WARNING: container name already exists (ignoring --image)")
args.full_name = args.name + args.suffix
initing = False
if len(containers(container_filter=args.name)) == 0:
volumes = []
if "volumes" in args.ini:
volumes = volumes + args.ini["volumes"].split(",")
volumes = [v.split(":") for v in volumes]
binds = {v[0].strip():{"bind":v[1].strip(),"mode":v[2].strip()} for v in volumes}
volumes = [v[1] for v in volumes]
host_config = cli.create_host_config(
binds=binds,
restart_policy={'Name' : 'unless-stopped'})
#cli.pull(args.image)
userpwd = getpwnam(user)
cli.create_container(args.image,
stdin_open=True,
tty=True,
name=args.full_name,
hostname=args.name,
labels={'group': prog, 'user': user},
volumes=volumes,
working_dir=args.home,
environment={
"HOST_USER_ID": userpwd.pw_uid,
"HOST_USER_GID": userpwd.pw_gid,
"HOST_USER_NAME": user
},
host_config=host_config
)
initing=True
cli.start(args.full_name)
if initing:
print("")
print("Initializing...")
#os.popen('docker exec '+args.full_name + ' /bin/bash -c "if [ -e /init-user ]; then /init-user; else echo \"No Initialization skript found for container\"; fi; echo Initialization finished."').read().split(":")[-1]
init_cmd = 'docker exec '+args.full_name + ' /bin/bash -c "if [ -e /init-user ]; then /init-user; else echo \\\"Script...\\\"; fi; echo Initialization finished."'
print(os.popen(init_cmd).read())
#print("Please login again.")
#sys.exit(0)
if len(args.cmd) == 0:
try:
print(args.greeting.replace("```",""))
except UnicodeEncodeError:
print(hostname)
user_bash = os.popen('docker exec -u root '+args.full_name + ' getent passwd '+user+'').read().split(":")[-1]
if user_bash == "":
user_bash = "/bin/bash"
cmd = args.cmd if args.cmd else user_bash
#custom
#user_bash = "/bin/sh"
#cmd = user_bash
cmd = "/bin/bash -c \"" + cmd + "\""
#custom
#os.system('docker exec -u '+user+' '+ args.full_name + ' ' + 'useradd -s /bin/bash user')
# a tty needs -it, scp needs -i
docker_arg = "-i" if not sys.stdout.isatty() or is_scp_cmd else "-it"
os.system('docker exec -u '+user+" " + docker_arg +' '+ args.full_name + ' ' + cmd+"")
if args.temp:
cli.remove_container(args.full_name, v=True, force=True)
cli.close()

View File

@ -0,0 +1,9 @@
# Original by https://github.com/sleeepyjack/dockersh
# Modified by georg@lysergic.dev
# Note! This is a skeleton, it is being altered by the spawn process.
FROM lc-archlinux-userbase-v2:sh0
COPY user-mapping.sh /
RUN chmod u+x /user-mapping.sh
ENTRYPOINT ["/user-mapping.sh"]

View File

@ -0,0 +1,21 @@
#!/bin/bash
# Original by https://github.com/sleeepyjack/dockersh
# Modified by georg@lysergic.dev
if [ -z "${HOST_USER_NAME}" -o -z "${HOST_USER_ID}" -o -z "${HOST_USER_GID}" ]; then
echo "HOST_USER_NAME, HOST_USER_ID & HOST_USER_GID needs to be set!"; exit 100
fi
useradd \
--uid ${HOST_USER_ID} \
--gid ${HOST_USER_GID} \
--create-home \
--shell /bin/bash \
${HOST_USER_NAME}
groupadd --gid "${HOST_USER_GID}" "${HOST_USER_NAME}"
usermod -aG sudo ${HOST_USER_NAME}
sleep 5s
echo ${HOST_USER_NAME}:${HOST_USER_NAME} | chpasswd
exec su - "${HOST_USER_NAME}"