commit c27753da863b3c44ceb81b2fe6ac689550704f89 Author: Georg Date: Sat Aug 14 10:02:44 2021 +0200 Init Signed-off-by: Georg diff --git a/README.md b/README.md new file mode 100644 index 0000000..c151c16 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Hosts configurations related to our POC shell service. diff --git a/dockersh/install-custom.sh b/dockersh/install-custom.sh new file mode 100644 index 0000000..aed138e --- /dev/null +++ b/dockersh/install-custom.sh @@ -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/ diff --git a/etc/dockersh.ini b/etc/dockersh.ini new file mode 100644 index 0000000..90b5a81 --- /dev/null +++ b/etc/dockersh.ini @@ -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 diff --git a/etc/motd b/etc/motd new file mode 100644 index 0000000..4430e44 --- /dev/null +++ b/etc/motd @@ -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 ## +## ## +################################################################# diff --git a/etc/sshd/sshd-banner b/etc/sshd/sshd-banner new file mode 100644 index 0000000..ac788a5 --- /dev/null +++ b/etc/sshd/sshd-banner @@ -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). diff --git a/lcpubsh/bin/adduser.sh b/lcpubsh/bin/adduser.sh new file mode 100644 index 0000000..53e018d --- /dev/null +++ b/lcpubsh/bin/adduser.sh @@ -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 diff --git a/lcpubsh/bin/generate.sh b/lcpubsh/bin/generate.sh new file mode 100644 index 0000000..299cab6 --- /dev/null +++ b/lcpubsh/bin/generate.sh @@ -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 + diff --git a/lcpubsh/bin/make_lc_user_image.sh b/lcpubsh/bin/make_lc_user_image.sh new file mode 100644 index 0000000..fb0aa8a --- /dev/null +++ b/lcpubsh/bin/make_lc_user_image.sh @@ -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 . + diff --git a/lcpubsh/bin/pubsh b/lcpubsh/bin/pubsh new file mode 100755 index 0000000..b40f143 --- /dev/null +++ b/lcpubsh/bin/pubsh @@ -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() diff --git a/lcpubsh/image_template/Dockerfile b/lcpubsh/image_template/Dockerfile new file mode 100644 index 0000000..85b1008 --- /dev/null +++ b/lcpubsh/image_template/Dockerfile @@ -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"] diff --git a/lcpubsh/image_template/user-mapping.sh b/lcpubsh/image_template/user-mapping.sh new file mode 100644 index 0000000..f2aa456 --- /dev/null +++ b/lcpubsh/image_template/user-mapping.sh @@ -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}" +