Запуск произвольного недружественного кода Python на моем сервере
Я делаю игру, в которой пользователи могут писать программы на Python для управления роботами, которые сражаются друг с другом. Каждый ход (в игре за тысячу ходов) их сценарий будет запускаться на моем сервере, чтобы определить следующий ход робота. Как я могу предотвратить попадание этих пользователей на мой сервер?
Я подумал / исследовал следующее:
eval
их код в ограниченной среде (т.е. с__builtins__
отключен)- используя специфичные для ОС тюрьмы, такие как
chroot
/ptrace
- запустить код в виртуальной машине какой-то
У меня есть программа на Python, которая запускает пользовательский скрипт тысячу раз. Как я могу обеспечить максимальную общую продолжительность в одну минуту, ограничить ресурсы их памяти, запретить им доступ к любым файлам или сетевым подключениям и т. Д.? Каким было бы идеальное решение или комбинация решений?
3 ответа
Я работаю над PythonAnywhere, который представляет собой платформу как платформу Python, что означает, что мы выполняем много ненадежного кода от имени наших пользователей - новая интерактивная консоль на первой странице Python.org является нашей, и его ограничения, вероятно, не слишком далеко от вашего.
Я рекомендую использовать виртуализацию на уровне ОС; все на уровне языка, вероятно, будет менее безопасным. Мы используем chroot и cgroups по историческим причинам, но если бы мы начинали с нуля сегодня, я думаю, что мы использовали бы контейнеры Linux (LXC) или Docker. LXC - это, по сути, куча умных оболочек для chroot и cgroups, и так далее, что облегчает их использование, так что вы можете действительно быстро раскрутить эфемерную виртуальную машину. А Docker - еще более простая в использовании оболочка для LXC. Оба супер-быстрые - вы можете запустить новую виртуальную машину менее чем за секунду.
Я посмотрел на ваш профиль и решил, что это постоянный труд любви! так что здесь идет..
Учитывая ваши требования, я предполагаю, что вы планируете что-то, что будет доступно через i/webs.
Относительно вашей второй строки запроса (; которая предотвращает дублирование этого Q;):
У меня есть программа на Python, которая запускает пользовательский скрипт тысячу раз. Как я могу обеспечить максимальную общую продолжительность в одну минуту, ограничить ресурсы их памяти, запретить им доступ к любым файлам или сетевым подключениям и т. Д.? Каким было бы идеальное решение или комбинация решений?
Как вы, наверное, знаете, это хорошо протоптанная земля, восходящая к 1980-м годам. Повторное изобретение иногда, но не всегда лучше, из информированного POV. Для идей о том, как разработать временные инструкции для игроков и так далее, вы можете взглянуть на источники некоторых связанных текущих проектов, которые уже существуют в дебрях. например:
Спойлер:
- Робокод
Этот проект имеет долгую историю; находится в руках сообщества с 2005 года и активно поддерживается. Источники - Java. важно чтение имо.
https://github.com/robo-code/robocode
- код боя:
Это аромат javascript, работающий в Интернете. так что это должно быть очень полезно, помогая вам в этом с вашим питоническим подходом онлайн. сайт теперь является коммерческим / закрытым. тем не менее, вы можете увидеть исходные источники, которые были представлены для игры на github 2012. По крайней мере, это может дать идеи о том, как представить ваш игровой контент миру:)
https://github.com/timehome/game-off-2012
(кстати, код боя стал вторым!)
Ваша первая строка запроса на всем протяжении ТАК. Смотрите здесь. Тем не мение:
Самый дешевый (во всех смыслах) вариант будет chroot
острог. Следующим самым дешевым вариантом будет что-то вроде Linux-VServer. Производительность, по-видимому, близка к родной, хотя у меня нет опыта использования этого программного обеспечения. В противном случае, да, полная виртуализация с использованием xen или чего-либо еще.
имо:
Полная виртуализация привнесет головные боли и непосильные накладные расходы.
Если вы осторожны, вы можете сделать это с помощью простого chroot
острог.
Иметь тюрьму исключительно как одноразовую "арену" для запуска кодов. убедитесь, что он работает с минимальными ресурсами и привилегиями, необходимыми для выполнения работы.
Полностью отделите ваше веб-приложение от тюрьмы (например, для тюрьмы не должно быть доступа к сети и т. Д.).
Таким образом, рабочий процесс может быть таким:
загружать игровые запросы через ваше веб-приложение.
благослови игровые запросы. вы сказали " произвольные недружественные коды ", но на самом деле игра будет иметь некоторые формальные параметры; вы только будете хотеть подмножество
python
функциональность. по крайней мере, проверить это.выстраивать в очередь явно вменяемые игровые запросы и отправлять их на арену (ака тюрьма).
если вы действительно параноик, создайте новую чистую арену / тюрьму для каждой новой игры.
запустить игру на арене. сохранение каждого отдельного игрового состояния (во внутреннем недорогом формате) в базе данных, локальной для тюрьмы.
снаружи тюрьмы, и когда игра будет завершена, предоставьте веб-приложению базу данных о состоянии игры, а затем передайте ее на веб-сайт i / we.
все сделано. иди и выпей!
Примечания по настройке тюрьмы Python.
Вот как я попал в этот пост. Я искал конкретные инструкции о том, как заключить в тюрьму python
используя (очень отличный) джейлкит. но не мог найти много.. поэтому, приготовив свой собственный, я тогда посмотрел, есть ли место для этого на SO.
Имейте в виду, что некоторые дистрибутивы nix, такие как Unbuntu и Centos, используют python
в их работе. в этом случае, чтобы избежать путаницы в вашей системе, вы можете захотеть собрать любую версию python
требуется из источников.
Вот мой рецепт для установки python 2.7
(предполагается, что у вас уже установлен джейлкит) в Centos
Во-первых, более формальный подход к джейлкиту, включая создание выделенного пользователя, может быть следующим:
# Once-only set-up:
# as root user:
## get, build and alt-install required python onto host OS
mkdir -p /usr/local/src/python
cd /usr/local/src/python
wget http://www.python.org/ftp/python/2.7/Python-2.7.tgz
tar -vxzf Python-2.7.tgz
cd Python-2.7
./configure #default ${prefix}="/usr/local"
make
make altinstall #don't clobber systems' python; everythin installed under /urs/local and comes with 2.7 postfix.
## set-up a jailkit config for python 2.7 (& optionally to support some minimal devs)
cat <<OOOK >> /etc/jailkit/jk_init.ini
[python2.7]
comment = python 2.7 interpreter and libraries
paths = python2.7, /usr/local/lib/python2.7, /usr/local/include/python2.7, /etc/ld.so.conf
devices = /dev/null, /dev/zero, /dev/random, /dev/urandom
OOOK
# Ad-infinitum:
# as root user:
cd /home
## create a python jail by passing jk_init the name of the sction created in /etc/jailkit/jk_init.ini
jk_init -v -j /home/jail_py python2.7
## optionally create a dedicated user
useradd -M -s /sbin/nologin prisoner
passwd prisoner
## quick sanity-check for setuids
find /home/jail_py -perm -4000 -exec ls -ldb {} \;
## test
echo "import sys; print sys.version" | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/local/bin/python2.7
echo "import os; print 'cwd:{} uid:{}'.format(os.getcwd(),os.getuid())" | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/local/bin/python2.7
jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/local/bin/python2.7
## optional, if we need /proc
mkdir -p /home/jail_py/proc
mount -t proc /proc /home/jail_py/proc
## nuke everything
umount /home/jail_py/proc
rm -fr /home/jail_py
userdel prisoner
И вот не рекомендуемый, более специальный подход
# Once-only set-up:
# as root user:
## get and build required python
mkdir -p /usr/local/src/python
cd /usr/local/src/python
wget http://www.python.org/ftp/python/2.7/Python-2.7.tgz
tar -vxzf Python-2.7.tgz
cd Python-2.7
./configure --prefix="/usr" # optionally override default ${prefix} ~ we don't really need /local/ indirection for our jail
make
## find out minimal python lib dependencies so we can add them to the jail - see jk_cp -j below..
ldd /home/jail_py/usr/bin/python
# Ad-infinitum:
# as root user:
## create a user
useradd -M -s /sbin/nologin prisoner
passwd prisoner
## manually create a jail
mkdir -p /home/jail_py
## deploy python into jail
# IMPORTANT: be sure to export DESTDIR to avoid stomping on your system python ;)
cd /usr/local/src/python/Python-2.7
export DESTDIR="/home/jail_py" # point install to /home/jail_py/${prefix}
make install # no need for altinstall since we're deploying directly to the jail
cd /home
## provision jail with a copy of /etc/ld.so.conf to prevent jk_cp form chucking out warnings
jk_cp -j /home/jail_py /etc/ld.so.conf
## copy minimal python lib dependencies to the jail - see ldd above
jk_cp -j /home/jail_py /lib/libpthread.so.0 /lib/libdl.so.2 /lib/libutil.so.1 /lib/libm.so.6 /lib/libc.so.6 /lib/ld-linux.so.2
## quick sanity-check for setuids
find /home/jail_py -perm -4000 -exec ls -ldb {} \;
## test running jailed python as user 'prisoner'
echo "import sys; print sys.version" | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/bin/python
echo "import os; print 'cwd:{} uid:{}'.format(os.getcwd(),os.getuid())" | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/bin/python
jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/bin/python
## optional, if we need /proc
mkdir -p /home/jail_py/proc
mount -t proc /proc /home/jail_py/proc
#optional, if we need some /dev/x
jk_cp -j /home/jail_py /dev/null
#nuke everything
umount /home/jail_py/proc
rm -fr /home/jail_py
userdel prisoner
Примечание: в зависимости от ваших требований вы также можете / должны монтировать /proc
, Например, запустить что-то вроде этого не удастся, если /proc
* не установлен
cat <<OOOK | jk_chrootlaunch -u prisoner -j /home/jail_py -x /home/jail_py/usr/local/bin/python2.7 -
#thx: https://raw.github.com/pixelb/ps_mem/master/ps_mem.py
import os
import sys
import errno
class Proc:
def __init__(self):
uname = os.uname()
if uname[0] == "FreeBSD":
self.proc = '/compat/linux/proc'
else:
self.proc = '/proc'
def path(self, *args):
return os.path.join(self.proc, *(str(a) for a in args))
def open(self, *args):
try:
return open(self.path(*args))
except (IOError, OSError):
val = sys.exc_info()[1]
if (val.errno == errno.ENOENT or # kernel thread or process gone
val.errno == errno.EPERM):
raise LookupError
raise
def meminfo(self):
fd=self.open("meminfo")
for next in iter(fd.readline, ""):
print next.replace('\n', '')
Proc().meminfo()
OOOK
Вы, конечно, * не должны нуждаться в интерактивной сессии. Но все же, возможно, стоит запустить свою арену под преданным пользователем. Но ты не обязан. В любом случае, джейлкит берет на себя большую часть усилий по созданию достойного chroot
острог. Вы можете взять источники и возиться с ними в соответствии с вашими потребностями (я так и сделал). или вы можете просто использовать jk_chrootlaunch, чтобы безопасно вызвать вашу арену за пределами тюрьмы.
(NB: причина, по которой мне нужно было связываться с исходниками джейлкита, заключается в том, что он использует тот же взлом "/./", что и pure-ftp. Lol)
Наконец, вы можете прочитать: http://www.unixwiz.net/techtips/chroot-practices.html
Желаю удачи, и я надеюсь, что вы все еще активны в этом.. ~i41 хотел бы увидеть питонную реализацию =) мы привыкли играть в эту игру навязчиво в универе Mat-Peck c-zone stylee;)
Нет встроенного способа запуска изолированного кода в cpython, но есть в pypy.
Есть несколько других способов, описанных в вики Python (например, с помощью jailkit), но они, кажется, имеют различные недостатки.
https://wiki.python.org/moin/SandboxedPython
Я бы пошел по пыпу.