From 19f819c14250e234426c035365717b1a921ba81d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Wed, 4 Apr 2018 17:43:18 +0200 Subject: [PATCH 1/7] Implement TFW & challenge solver user separation --- Dockerfile | 7 +++++-- lib/tfw/components/terminado_event_handler.py | 3 ++- supervisor/supervisord.conf | 2 +- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 17fa426..f05fc33 100644 --- a/Dockerfile +++ b/Dockerfile @@ -53,14 +53,17 @@ COPY nginx/nginx.conf ${TFW_NGINX_CONF} COPY nginx/default.conf ${TFW_NGINX_DEFAULT} COPY lib ${TFW_LIB_DIR} +RUN for dir in "${TFW_LIB_DIR}" "/etc/nginx" "/etc/supervisor"; do \ + chown -R root:root "$dir" && chmod -R 700 "$dir"; \ + done + ONBUILD ARG BUILD_CONTEXT="." ONBUILD ARG NOFRONTEND="" ONBUILD COPY ${BUILD_CONTEXT}/nginx/components/ ${TFW_NGINX_COMPONENTS} ONBUILD COPY ${BUILD_CONTEXT}/supervisor/components/ ${TFW_SUPERVISORD_COMPONENTS} -ONBUILD RUN chown -R ${AVATAO_USER} /var/log/nginx /var/lib/nginx &&\ - for f in "${TFW_NGINX_DEFAULT}" ${TFW_NGINX_COMPONENTS}/*.conf; do \ +ONBUILD RUN for f in "${TFW_NGINX_DEFAULT}" ${TFW_NGINX_COMPONENTS}/*.conf; do \ envsubst "$(printenv | cut -d= -f1 | grep TFW_ | sed -e 's/^/$/g')" < $f > $f~ && mv $f~ $f ;\ done ONBUILD VOLUME ["/etc/nginx", "/var/lib/nginx", "/var/log/nginx"] diff --git a/lib/tfw/components/terminado_event_handler.py b/lib/tfw/components/terminado_event_handler.py index 50245f5..c4d49cc 100644 --- a/lib/tfw/components/terminado_event_handler.py +++ b/lib/tfw/components/terminado_event_handler.py @@ -5,6 +5,7 @@ from tfw.components.terminado_mini_server import TerminadoMiniServer from tfw.event_handler_base import TriggerlessEventHandler from tfw.config import TFWENV from tfw.config.logs import logging +from tao.config import TAOENV LOG = logging.getLogger(__name__) @@ -14,7 +15,7 @@ class TerminadoEventHandler(TriggerlessEventHandler): super().__init__(key) self.working_directory = TFWENV.TERMINADO_DIR self._historymonitor = monitor - self.terminado_server = TerminadoMiniServer('/terminal', TFWENV.TERMINADO_PORT, TFWENV.TERMINADO_WD, ['bash']) + self.terminado_server = TerminadoMiniServer('/terminal', TFWENV.TERMINADO_PORT, TFWENV.TERMINADO_WD, ['sudo', '-u', TAOENV.USER, 'bash']) self.commands = {'write': self.write, 'read': self.read} if self._historymonitor: diff --git a/supervisor/supervisord.conf b/supervisor/supervisord.conf index 4a14c2b..9efcd76 100644 --- a/supervisor/supervisord.conf +++ b/supervisor/supervisord.conf @@ -1,5 +1,5 @@ [supervisord] -user=user +user=root logfile = /tmp/supervisord.log loglevel = debug pidfile = /tmp/supervisord.pid From b54c91848bd88d13f2643dfed8d47ed589fee270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Wed, 4 Apr 2018 17:48:49 +0200 Subject: [PATCH 2/7] Remove tfw server init from baseimage (part of child contract now) --- supervisor/supervisord.conf | 4 ---- 1 file changed, 4 deletions(-) diff --git a/supervisor/supervisord.conf b/supervisor/supervisord.conf index 9efcd76..6e9e04c 100644 --- a/supervisor/supervisord.conf +++ b/supervisor/supervisord.conf @@ -18,9 +18,5 @@ command=/usr/sbin/nginx -g 'daemon off;' autostart=true autorestart=true -[program:app] -directory=%(ENV_TFW_APP_DIR)s -command=python3 app.py - [include] files=%(ENV_TFW_SUPERVISORD_COMPONENTS)s/*.conf From 35421649c912b2dbde3a4419b098377bbb1b25c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Thu, 5 Apr 2018 14:43:07 +0200 Subject: [PATCH 3/7] Extract spawning bash as user to a variable --- lib/tfw/components/terminado_event_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/tfw/components/terminado_event_handler.py b/lib/tfw/components/terminado_event_handler.py index c4d49cc..76621a4 100644 --- a/lib/tfw/components/terminado_event_handler.py +++ b/lib/tfw/components/terminado_event_handler.py @@ -15,7 +15,8 @@ class TerminadoEventHandler(TriggerlessEventHandler): super().__init__(key) self.working_directory = TFWENV.TERMINADO_DIR self._historymonitor = monitor - self.terminado_server = TerminadoMiniServer('/terminal', TFWENV.TERMINADO_PORT, TFWENV.TERMINADO_WD, ['sudo', '-u', TAOENV.USER, 'bash']) + bash_as_user_cmd = ['sudo', '-u', TAOENV.USER, 'bash'] + self.terminado_server = TerminadoMiniServer('/terminal', TFWENV.TERMINADO_PORT, TFWENV.TERMINADO_WD, bash_as_user_cmd) self.commands = {'write': self.write, 'read': self.read} if self._historymonitor: From b74ff39438a54f0aa0241c95a8841a580f8ac18f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Thu, 5 Apr 2018 14:43:39 +0200 Subject: [PATCH 4/7] Implement directory whitelisting in webide --- .../components/source_code_event_handler.py | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/tfw/components/source_code_event_handler.py b/lib/tfw/components/source_code_event_handler.py index 6bc020c..43b1972 100644 --- a/lib/tfw/components/source_code_event_handler.py +++ b/lib/tfw/components/source_code_event_handler.py @@ -1,7 +1,7 @@ # Copyright (C) 2018 Avatao.com Innovative Learning Kft. # All Rights Reserved. See LICENSE file for details. -from os.path import isfile, join, relpath, exists, isdir +from os.path import isfile, join, relpath, exists, isdir, realpath from glob import glob from fnmatch import fnmatchcase from collections import Iterable @@ -13,9 +13,10 @@ from tfw.config.logs import logging LOG = logging.getLogger(__name__) -class FileManager: - def __init__(self, working_directory, selected_file=None, exclude=None): +class FileManager: # pylint: disable=too-many-instance-attributes + def __init__(self, working_directory, allowed_directories=None, selected_file=None, exclude=None): self._exclude, self.exclude = None, exclude + self._allowed_directories, self.allowed_directories = None, allowed_directories self._workdir, self.workdir = None, working_directory self._filename, self.filename = None, selected_file or self.files[0] @@ -39,8 +40,19 @@ class FileManager: def workdir(self, directory): if not exists(directory) or not isdir(directory): raise EnvironmentError('"{}" is not a directory!'.format(directory)) + if self.allowed_directories: + if realpath(directory) not in self._allowed_directories: + raise EnvironmentError('Directory "{}" is not in whitelist!'.format(directory)) self._workdir = directory + @property + def allowed_directories(self): + return self._allowed_directories + + @allowed_directories.setter + def allowed_directories(self, directories): + self._allowed_directories = directories + @property def filename(self): return self._filename @@ -75,9 +87,11 @@ class FileManager: class SourceCodeEventHandler(TriggerlessEventHandler): - def __init__(self, key, directory, selected_file=None, exclude=None): + # pylint: disable=too-many-arguments + def __init__(self, key, directory, allowed_directories=None, selected_file=None, exclude=None): super().__init__(key) - self.filemanager = FileManager(directory, selected_file=selected_file, exclude=exclude) + self.filemanager = FileManager(allowed_directories=allowed_directories, working_directory=directory, + selected_file=selected_file, exclude=exclude) self.commands = {'read': self.read, 'write': self.write, From b73b7307bdcf0e22ac8f5273b14bbaab1effcdd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Thu, 5 Apr 2018 17:01:50 +0200 Subject: [PATCH 5/7] Improve webide whitelisting by enforcing stricter rules (fix symlink attack) --- lib/tfw/components/source_code_event_handler.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/tfw/components/source_code_event_handler.py b/lib/tfw/components/source_code_event_handler.py index 43b1972..4b6662c 100644 --- a/lib/tfw/components/source_code_event_handler.py +++ b/lib/tfw/components/source_code_event_handler.py @@ -41,7 +41,7 @@ class FileManager: # pylint: disable=too-many-instance-attributes if not exists(directory) or not isdir(directory): raise EnvironmentError('"{}" is not a directory!'.format(directory)) if self.allowed_directories: - if realpath(directory) not in self._allowed_directories: + if not self._is_whitelisted(directory): raise EnvironmentError('Directory "{}" is not in whitelist!'.format(directory)) self._workdir = directory @@ -67,6 +67,7 @@ class FileManager: # pylint: disable=too-many-instance-attributes def files(self): return [self._relpath(file) for file in glob(join(self._workdir, '**/*'), recursive=True) if isfile(file) and + self._is_whitelisted(file) and not any(fnmatchcase(file, blacklisted) for blacklisted in self.exclude)] @property @@ -79,6 +80,9 @@ class FileManager: # pylint: disable=too-many-instance-attributes with open(self._filepath(self.filename), 'w', errors='surrogateescape') as ofile: ofile.write(value) + def _is_whitelisted(self, file): + return any(realpath(file).startswith(allowed_dir) for allowed_dir in self.allowed_directories) + def _filepath(self, filename): return join(self._workdir, filename) From bc340e2e19ba6a3911f363f597861dd371c4c501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Thu, 5 Apr 2018 17:16:41 +0200 Subject: [PATCH 6/7] Enforce webide whitelisting --- lib/tfw/components/source_code_event_handler.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/tfw/components/source_code_event_handler.py b/lib/tfw/components/source_code_event_handler.py index 4b6662c..6afcf63 100644 --- a/lib/tfw/components/source_code_event_handler.py +++ b/lib/tfw/components/source_code_event_handler.py @@ -14,7 +14,7 @@ LOG = logging.getLogger(__name__) class FileManager: # pylint: disable=too-many-instance-attributes - def __init__(self, working_directory, allowed_directories=None, selected_file=None, exclude=None): + def __init__(self, working_directory, allowed_directories, selected_file=None, exclude=None): self._exclude, self.exclude = None, exclude self._allowed_directories, self.allowed_directories = None, allowed_directories self._workdir, self.workdir = None, working_directory @@ -40,9 +40,8 @@ class FileManager: # pylint: disable=too-many-instance-attributes def workdir(self, directory): if not exists(directory) or not isdir(directory): raise EnvironmentError('"{}" is not a directory!'.format(directory)) - if self.allowed_directories: - if not self._is_whitelisted(directory): - raise EnvironmentError('Directory "{}" is not in whitelist!'.format(directory)) + if not self._is_whitelisted(directory): + raise EnvironmentError('Directory "{}" is not in whitelist!'.format(directory)) self._workdir = directory @property @@ -92,7 +91,7 @@ class FileManager: # pylint: disable=too-many-instance-attributes class SourceCodeEventHandler(TriggerlessEventHandler): # pylint: disable=too-many-arguments - def __init__(self, key, directory, allowed_directories=None, selected_file=None, exclude=None): + def __init__(self, key, directory, allowed_directories, selected_file=None, exclude=None): super().__init__(key) self.filemanager = FileManager(allowed_directories=allowed_directories, working_directory=directory, selected_file=selected_file, exclude=exclude) From bd84e4fe06215026b36fd3aec2c4cbb28f89f5c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krist=C3=B3f=20T=C3=B3th?= Date: Thu, 5 Apr 2018 17:19:56 +0200 Subject: [PATCH 7/7] Improve code formatting in webide --- lib/tfw/components/source_code_event_handler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/tfw/components/source_code_event_handler.py b/lib/tfw/components/source_code_event_handler.py index 6afcf63..fbaffd8 100644 --- a/lib/tfw/components/source_code_event_handler.py +++ b/lib/tfw/components/source_code_event_handler.py @@ -65,9 +65,9 @@ class FileManager: # pylint: disable=too-many-instance-attributes @property def files(self): return [self._relpath(file) for file in glob(join(self._workdir, '**/*'), recursive=True) - if isfile(file) and - self._is_whitelisted(file) and - not any(fnmatchcase(file, blacklisted) for blacklisted in self.exclude)] + if isfile(file) + and self._is_whitelisted(file) + and not any(fnmatchcase(file, blacklisted) for blacklisted in self.exclude)] @property def file_contents(self):