diff options
-rw-r--r-- | .dockerignore | 26 | ||||
-rw-r--r-- | .flake8 | 2 | ||||
-rw-r--r-- | .gitlab-ci.yml | 26 | ||||
-rw-r--r-- | Pipfile | 21 | ||||
-rw-r--r-- | Pipfile.lock | 276 | ||||
-rw-r--r-- | README.md | 58 | ||||
-rw-r--r-- | azure-pipelines.yml | 59 | ||||
-rw-r--r-- | binaries/nsjail2.5-alpine-x86_64 | bin | 678704 -> 0 bytes | |||
-rw-r--r-- | binaries/nsjail2.6-ubuntu-x86_64 | bin | 750328 -> 0 bytes | |||
-rw-r--r-- | config.py | 34 | ||||
-rw-r--r-- | docker-compose.yml | 43 | ||||
-rw-r--r-- | docker/Dockerfile | 11 | ||||
-rw-r--r-- | docker/Dockerfile.webapp | 25 | ||||
-rw-r--r-- | docker/base.Dockerfile | 42 | ||||
-rw-r--r-- | docker/venv.Dockerfile | 12 | ||||
-rw-r--r-- | rmq.py | 111 | ||||
-rw-r--r-- | snekbox/__init__.py (renamed from logs.py) | 0 | ||||
-rw-r--r-- | snekbox/nsjail.py (renamed from snekbox.py) | 56 | ||||
-rw-r--r-- | snekbox/site/snekapp.py | 35 | ||||
-rw-r--r-- | snekbox/site/templates/index.html | 14 | ||||
-rw-r--r-- | snekbox/site/templates/result.html | 9 | ||||
-rw-r--r-- | snekweb.py | 72 | ||||
-rw-r--r-- | templates/index.html | 106 | ||||
-rw-r--r-- | tests/test_snekbox.py | 38 |
24 files changed, 278 insertions, 798 deletions
diff --git a/.dockerignore b/.dockerignore index 8914ea8..2a5ccec 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,18 +1,8 @@ -.venv -scripts -htmlcov -__pycache__ -.vagrant -.pytest_cache -.git -.github -.cache -Vagrantfile -.coverage -.coveragerc -.gitignore -.travis.yml -docker -docker-compose.yml -LICENSE -README.md +# Exclude everything +* + +# Make exceptions for what's needed +!snekbox +!Pipfile +!Pipfile.lock +!LICENSE @@ -1,6 +1,6 @@ [flake8] max-line-length=100 -application_import_names=snekbox,config,logs +application_import_names=snekbox ignore= P102,B311,W503,E226,S311, # Missing Docstrings diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index 2dc9608..0000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,26 +0,0 @@ -image: pythondiscord/snekbox-ci:latest - -variables: - PIPENV_CACHE_DIR: "$CI_PROJECT_DIR/pipenv-cache" - -cache: - paths: - - "$CI_PROJECT_DIR/pipenv-cache" - - "$CI_PROJECT_DIR/.venv" - -services: - - docker:dind - -stages: - - build - -build: - tags: - - docker - - pythondiscord - stage: build - script: - - pipenv install --dev --deploy - - pipenv run lint - - pipenv run test - - sh scripts/deploy.sh @@ -4,16 +4,10 @@ verify_ssl = true name = "pypi" [packages] -pika = "*" -docker = "*" -urllib3 = ">=1.24.2,<1.25" - -[dev-packages] flask = "*" -flask-sockets = "*" -gevent = "==1.2.2" -gevent-websocket = "*" gunicorn = "*" + +[dev-packages] pytest = "*" pytest-cov = "*" pytest-dependency = "*" @@ -28,18 +22,15 @@ flake8-string-format = "*" flake8-formatter-junit-xml = "*" [requires] -python_version = "3.6" +python_version = "3.7" [scripts] lint = "flake8" precommit = "pre-commit install" -test = "py.test tests --cov . --cov-report term-missing -v" -report = "py.test tests --cov . --cov-report=html" -snekbox = "python snekbox.py" -snekweb = "gunicorn -w 2 -b 0.0.0.0:5000 --log-level debug -k geventwebsocket.gunicorn.workers.GeventWebSocketWorker snekweb:app" +test = "pytest tests --cov . --cov-report term-missing -v" +report = "pytest tests --cov . --cov-report=html" +snekbox = "gunicorn -w 2 -b 0.0.0.0:8060 snekbox.site.snekapp:app" buildbox = "docker build -t pythondiscord/snekbox:latest -f docker/Dockerfile ." pushbox = "docker push pythondiscord/snekbox:latest" buildboxbase = "docker build -t pythondiscord/snekbox-base:latest -f docker/base.Dockerfile ." pushboxbase = "docker push pythondiscord/snekbox-base:latest" -buildweb = "docker build -t pythondiscord/snekboxweb:latest -f docker/Dockerfile.webapp ." -pushweb = "docker push pythondiscord/snekboxweb:latest" diff --git a/Pipfile.lock b/Pipfile.lock index 262131c..466a42b 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,11 +1,11 @@ { "_meta": { "hash": { - "sha256": "22c5372766fde7766ebb92d523cfc1eb0192bf1cc2b4860c51b50937998da40d" + "sha256": "814185e2e1b964ab58af9a9df416ace7b5b416475d828ec9b31a9dfecb5693e1" }, "pipfile-spec": 6, "requires": { - "python_version": "3.6" + "python_version": "3.7" }, "sources": [ { @@ -16,78 +16,82 @@ ] }, "default": { - "certifi": { - "hashes": [ - "sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5", - "sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae" - ], - "version": "==2019.3.9" - }, - "chardet": { + "click": { "hashes": [ - "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", - "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" ], - "version": "==3.0.4" + "version": "==7.0" }, - "docker": { + "flask": { "hashes": [ - "sha256:2b1f48041cfdcc9f6b5da0e04e0e326ded225e736762ade2060000e708f4c9b7", - "sha256:c456ded5420af5860441219ff8e51cdec531d65f4a9e948ccd4133e063b72f50" + "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", + "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" ], "index": "pypi", - "version": "==3.7.2" - }, - "docker-pycreds": { - "hashes": [ - "sha256:6ce3270bcaf404cc4c3e27e4b6c70d3521deae82fb508767870fdbf772d584d4", - "sha256:7266112468627868005106ec19cd0d722702d2b7d5912a28e19b826c3d37af49" - ], - "version": "==0.4.0" - }, - "idna": { - "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" - ], - "version": "==2.8" + "version": "==1.0.2" }, - "pika": { + "gunicorn": { "hashes": [ - "sha256:0c50285f00a8b4816f2c9a44469107d9e738ba3a90386f14b625d8cceef4f6ae", - "sha256:5ba83d3daffccb92788d24facdab62a3db6aa03b8a6d709b03dc792d35c0dfe8" + "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", + "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" ], "index": "pypi", - "version": "==1.0.1" + "version": "==19.9.0" }, - "requests": { + "itsdangerous": { "hashes": [ - "sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e", - "sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b" + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" ], - "version": "==2.21.0" + "version": "==1.1.0" }, - "six": { + "jinja2": { "hashes": [ - "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", - "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + "sha256:74c935a1b8bb9a3947c50a54766a969d4846290e1e788ea44c1392163723c3bd", + "sha256:f84be1bb0040caca4cea721fcbbbbd61f9be9464ca236387158b0feea01914a4" ], - "version": "==1.12.0" + "version": "==2.10" }, - "urllib3": { + "markupsafe": { "hashes": [ - "sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0", - "sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3" + "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", + "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", + "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", + "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", + "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", + "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", + "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", + "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", + "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", + "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", + "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", + "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", + "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", + "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", + "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", + "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", + "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", + "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", + "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", + "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", + "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", + "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", + "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", + "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", + "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", + "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", + "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", + "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" ], - "index": "pypi", - "version": "==1.24.2" + "version": "==1.1.1" }, - "websocket-client": { + "werkzeug": { "hashes": [ - "sha256:1151d5fb3a62dc129164292e1227655e4bbc5dd5340a5165dfae61128ec50aa9", - "sha256:1fd5520878b68b84b5748bb30e592b10d0a91529d5383f74f4964e72b297fd3a" + "sha256:96da23fa8ccecbc3ae832a83df5c722c11547d021637faacb0bec4dd2f4666c8", + "sha256:ca5c2dcd367d6c0df87185b9082929d255358f5391923269335782b213d52655" ], - "version": "==0.56.0" + "version": "==0.15.1" } }, "develop": { @@ -114,17 +118,10 @@ }, "cfgv": { "hashes": [ - "sha256:6e9f2feea5e84bc71e56abd703140d7a2c250fc5ba38b8702fd6a68ed4e3b2ef", - "sha256:e7f186d4a36c099a9e20b04ac3108bd8bb9b9257e692ce18c8c3764d5cb12172" + "sha256:39f8475d8eca48639f900daffa3f8bd2f60a31d989df41a9f81c5ad1779a66eb", + "sha256:a6a4366d32799a6bfb6f577ebe113b27ba8d1bae43cb57133b1472c1c3dae227" ], - "version": "==1.6.0" - }, - "click": { - "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" - ], - "version": "==7.0" + "version": "==1.5.0" }, "coverage": { "hashes": [ @@ -239,89 +236,6 @@ "index": "pypi", "version": "==0.7" }, - "flask": { - "hashes": [ - "sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48", - "sha256:a080b744b7e345ccfcbc77954861cb05b3c63786e93f2b3875e0913d44b43f05" - ], - "index": "pypi", - "version": "==1.0.2" - }, - "flask-sockets": { - "hashes": [ - "sha256:072927da8edca0e81e024f5787e643c87d80b351b714de95d723becb30e0643b", - "sha256:350a76d55f5889f64afd2ca9b32f262680b7960965f0830365576307d30cfe1e" - ], - "index": "pypi", - "version": "==0.2.1" - }, - "gevent": { - "hashes": [ - "sha256:0901975628790e8a57fc92bb7062e5b856edea48c8de9caf36cfda14eae07329", - "sha256:1af93825db5753550fa8ff5ab2f2132e8733170b3f8d38347b34fa4a984cb624", - "sha256:2ff045a91509c35664c27a849c8cbf742a227f587b7cdbc88301e9c85dcaedff", - "sha256:35790f1a3c8e431ada3471b70bb2105050009ea4beb15cbe41b86bc716a7ffa9", - "sha256:4791c8ae9c57d6f153354736e1ccab1e2baf6c8d9ae5a77a9ac90f41e2966b2d", - "sha256:552719cec4721673b8c7d2f9de666e3f7591b9b182f801ecaef1c76e638052aa", - "sha256:59e9237af027f8db85e5d78a9da2e328ae96f01d67a0d62abcecad3db7876908", - "sha256:60109741377367eef8ded9283a1bf629621b73acaf3e1e8aac9d1a0f50fa0f05", - "sha256:70558dd45c7a1f8046ba45792e489dd0f409bd8a3b7a0635ca9d3055223b3dff", - "sha256:81cb24e0f7bd9888596364e8d8ed0d65c2547c84884c67bb46d956faeed67396", - "sha256:833bebdc36bfeeedefc200ca9aee9b8eddd80f56b63ca1e886e18b97b1240edd", - "sha256:8a710eddb3e9e5f22bdbd458b5f211b94f59409ecd6896f15b9fee2cba266a59", - "sha256:9b492bb1a043540abb6e54fdb5537531e24962ca49c09f3b47dc4f9c37f6297c", - "sha256:a16db4f56699ef07f0249b953ff949aae641e50b2bdc4710f11c0d8d9089b296", - "sha256:a66cf99f08da65c501826a19e30f5a6e7ba942fdd79baba5ce2d51eebaa13444", - "sha256:b67a10799923f9fed546ca5f8b93a2819c71a60132d7a97b4a13fbdab66b278a", - "sha256:b7e0e6400c2f3ce78a9ae1cdd55b53166feedd003d60c033863881227129a4d3", - "sha256:c9dd6534c46ed782e2d7236767cd07115cb29ce8670c2fc0794f264de9024fe0", - "sha256:de13a8e378103af84a8bf6015ad1d2761d46f29b8393e8dd6d9bb7cb51bbb713", - "sha256:deafd70d04ab62428d4e291e8e2c0fb22f38690e6a9f23a67ee6c304087634da", - "sha256:df52e06a2754c2d905aad75a7dc06a732c804d9edbc87f06f47c8f483ba98bca" - ], - "index": "pypi", - "version": "==1.2.2" - }, - "gevent-websocket": { - "hashes": [ - "sha256:17b67d91282f8f4c973eba0551183fc84f56f1c90c8f6b6b30256f31f66f5242", - "sha256:7eaef32968290c9121f7c35b973e2cc302ffb076d018c9068d2f5ca8b2d85fb0" - ], - "index": "pypi", - "version": "==0.10.1" - }, - "greenlet": { - "hashes": [ - "sha256:000546ad01e6389e98626c1367be58efa613fa82a1be98b0c6fc24b563acc6d0", - "sha256:0d48200bc50cbf498716712129eef819b1729339e34c3ae71656964dac907c28", - "sha256:23d12eacffa9d0f290c0fe0c4e81ba6d5f3a5b7ac3c30a5eaf0126bf4deda5c8", - "sha256:37c9ba82bd82eb6a23c2e5acc03055c0e45697253b2393c9a50cef76a3985304", - "sha256:51503524dd6f152ab4ad1fbd168fc6c30b5795e8c70be4410a64940b3abb55c0", - "sha256:8041e2de00e745c0e05a502d6e6db310db7faa7c979b3a5877123548a4c0b214", - "sha256:81fcd96a275209ef117e9ec91f75c731fa18dcfd9ffaa1c0adbdaa3616a86043", - "sha256:853da4f9563d982e4121fed8c92eea1a4594a2299037b3034c3c898cb8e933d6", - "sha256:8b4572c334593d449113f9dc8d19b93b7b271bdbe90ba7509eb178923327b625", - "sha256:9416443e219356e3c31f1f918a91badf2e37acf297e2fa13d24d1cc2380f8fbc", - "sha256:9854f612e1b59ec66804931df5add3b2d5ef0067748ea29dc60f0efdcda9a638", - "sha256:99a26afdb82ea83a265137a398f570402aa1f2b5dfb4ac3300c026931817b163", - "sha256:a19bf883b3384957e4a4a13e6bd1ae3d85ae87f4beb5957e35b0be287f12f4e4", - "sha256:a9f145660588187ff835c55a7d2ddf6abfc570c2651c276d3d4be8a2766db490", - "sha256:ac57fcdcfb0b73bb3203b58a14501abb7e5ff9ea5e2edfa06bb03035f0cff248", - "sha256:bcb530089ff24f6458a81ac3fa699e8c00194208a724b644ecc68422e1111939", - "sha256:beeabe25c3b704f7d56b573f7d2ff88fc99f0138e43480cecdfcaa3b87fe4f87", - "sha256:d634a7ea1fc3380ff96f9e44d8d22f38418c1c381d5fac680b272d7d90883720", - "sha256:d97b0661e1aead761f0ded3b769044bb00ed5d33e1ec865e891a8b128bf7c656" - ], - "version": "==0.4.15" - }, - "gunicorn": { - "hashes": [ - "sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471", - "sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3" - ], - "index": "pypi", - "version": "==19.9.0" - }, "identify": { "hashes": [ "sha256:244e7864ef59f0c7c50c6db73f58564151d91345cd9b76ed793458953578cadd", @@ -331,24 +245,10 @@ }, "importlib-metadata": { "hashes": [ - "sha256:46fc60c34b6ed7547e2a723fc8de6dc2e3a1173f8423246b3ce497f064e9c3de", - "sha256:bc136180e961875af88b1ab85b4009f4f1278f8396a60526c0009f503a1a96ca" - ], - "version": "==0.9" - }, - "itsdangerous": { - "hashes": [ - "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", - "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + "sha256:a17ce1a8c7bff1e8674cb12c992375d8d0800c9190177ecf0ad93e0097224095", + "sha256:b50191ead8c70adfa12495fba19ce6d75f2e0275c14c5a7beb653d6799b512bd" ], - "version": "==1.1.0" - }, - "jinja2": { - "hashes": [ - "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", - "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" - ], - "version": "==2.10.1" + "version": "==0.8" }, "junit-xml": { "hashes": [ @@ -356,39 +256,6 @@ ], "version": "==1.8" }, - "markupsafe": { - "hashes": [ - "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", - "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", - "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", - "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", - "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", - "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", - "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", - "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", - "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", - "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", - "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", - "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", - "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", - "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", - "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", - "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", - "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", - "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", - "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", - "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", - "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", - "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", - "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", - "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", - "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", - "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", - "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", - "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7" - ], - "version": "==1.1.1" - }, "mccabe": { "hashes": [ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", @@ -419,11 +286,11 @@ }, "pre-commit": { "hashes": [ - "sha256:2576a2776098f3902ef9540a84696e8e06bf18a337ce43a6a889e7fa5d26c4c5", - "sha256:82f2f2d657d7f9280de9f927ae56886d60b9ef7f3714eae92d12713cd9cb9e11" + "sha256:d3d69c63ae7b7584c4b51446b0b583d454548f9df92575b2fe93a68ec800c4d3", + "sha256:fc512f129b9526e35e80d656a16a31c198f584c4fce3a5c739045b5140584917" ], "index": "pypi", - "version": "==1.15.2" + "version": "==1.14.4" }, "py": { "hashes": [ @@ -456,11 +323,11 @@ }, "pytest": { "hashes": [ - "sha256:3773f4c235918987d51daf1db66d51c99fac654c81d6f2f709a046ab446d5e5d", - "sha256:b7802283b70ca24d7119b32915efa7c409982f59913c1a6c0640aacf118b95f5" + "sha256:592eaa2c33fae68c7d75aacf042efc9f77b27c08a6224a4f59beab8d9a420523", + "sha256:ad3ad5c450284819ecde191a654c09b0ec72257a2c711b9633d677c71c9850c4" ], "index": "pypi", - "version": "==4.4.1" + "version": "==4.3.1" }, "pytest-cov": { "hashes": [ @@ -521,13 +388,6 @@ ], "version": "==16.4.3" }, - "werkzeug": { - "hashes": [ - "sha256:0a73e8bb2ff2feecfc5d56e6f458f5b99290ef34f565ffb2665801ff7de6af7a", - "sha256:7fad9770a8778f9576693f0cc29c7dcc36964df916b83734f4431c0e612a7fbc" - ], - "version": "==0.15.2" - }, "zipp": { "hashes": [ "sha256:55ca87266c38af6658b84db8cfb7343cdb0bf275f93c7afaea0d8e7a209c7478", @@ -7,21 +7,18 @@ Python sandbox runners for executing code in isolation aka snekbox The user sends a piece of python code to a snekbox, the snekbox executes the code and sends the result back to the users. ``` - +-------------+ +------------+ +-----------+ - input -> | |---------->| |-------->| | >----------+ - | WEBSERVER | | RABBITMQ | | SNEKBOX | execution | -result <- | |<----------| |<--------| | <----------+ - +-------------+ +------------+ +-----------+ - ^ ^ ^ - | | |- Executes python code - | | |- Returns result - | | +----------------------- - | | - | |- Message queues opens on demand and closes automatically - | +--------------------------------------------------------- + +-------------+ +-----------+ + input -> | |---------->| | >----------+ + | HTTP POST | | SNEKBOX | execution | +result <- | |<----------| | <----------+ + +-------------+ +-----------+ + ^ ^ + | |- Executes python code + | |- Returns result + | +----------------------- | - |- Uses websockets for asynchronous connection between webui and webserver - +------------------------------------------------------------------------- + |- HTTP POST Endpoint receives request and returns result + +--------------------------------------------------------- ``` @@ -36,6 +33,8 @@ result <- | |<----------| |<--------| | <------ | docker | 18.03.1-ce | | docker-compose | 1.21.2 | | nsjail | 2.5 | +| flask | 1.0.2 | +| gunicorn | 19.9 | _________________________________________ ## Setup local test @@ -83,38 +82,20 @@ python3.6 -ISq -c "print('test')" ## Development environment -Start a rabbitmq instance and get the container IP +Start the webserver with docker: ```bash -docker-compose up -d pdrmq -docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' rmq -# expected output with default setting: 172.17.0.2 -# If not, change the config.py file to match +docker-compose up -d ``` -rabbitmq webinterface: `http://localhost:15672` - -start the webserver - -```bash -docker-compose up -d pdsnkweb -netstat -plnt -# tcp 0.0.0.0:5000 LISTEN -``` - -`http://localhost:5000` - +Run locally with pipenv: ```bash pipenv run snekbox # for debugging -# or -docker-compose up pdsnk # for running the container ``` - +Visit: `http://localhost:8060` ________________________________________ ## Unit testing and lint -Make sure rabbitmq is running before running tests - ```bash pipenv run lint pipenv run test @@ -126,11 +107,6 @@ ________________________________________ ```bash # Build pipenv run buildbox -pipenv run buildweb - # Push pipenv run pushbox -pipenv run pushweb ``` - - diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e423b28..bd916a4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -11,7 +11,7 @@ jobs: - task: UsePythonVersion@0 displayName: 'Set Python version' inputs: - versionSpec: '3.6.x' + versionSpec: '3.7.x' addToPath: true - script: pip3 install pipenv @@ -32,7 +32,10 @@ jobs: - job: build displayName: 'Build' dependsOn: test - condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) + + variables: + BASE_CHANGED: true + VENV_CHANGED: true steps: - task: Docker@1 @@ -43,8 +46,58 @@ jobs: dockerRegistryEndpoint: 'DockerHub' command: 'login' + - script: | + REQUEST_URL="https://dev.azure.com/python-discord/${SYSTEM_TEAMPROJECTID}/_apis/build/builds?queryOrder=finishTimeDescending&resultFilter=succeeded&\$top=1&repositoryType=${BUILD_REPOSITORY_PROVIDER}&repositoryId=${BUILD_REPOSITORY_NAME}&branchName=${BUILD_SOURCEBRANCH}&api-version=5.0" + echo "Retrieving previous build's commit using $REQUEST_URL" + RESPONSE="$(curl -sSL "${REQUEST_URL}")" + + if [[ $BUILD_REASON = "PullRequest" ]]; then + PREV_COMMIT="$(echo "${RESPONSE}" | grep -Po '"pr\.sourceSha"\s*:\s*"\K.*?[^\\](?="\s*[,}])')" + if [[ -z $PREV_COMMIT ]]; then + echo "Could not retrieve the previous build's commit. Falling back to the head of the target branch." + PREV_COMMIT="origin/$SYSTEM_PULLREQUEST_TARGETBRANCH" + fi + else + PREV_COMMIT="$(echo "${RESPONSE}" | grep -Po '"sourceVersion"\s*:\s*"\K.*?[^\\](?="\s*[,}])')" + fi + + if [[ -n $PREV_COMMIT ]]; then + echo "Using $PREV_COMMIT to compare diffs." + + if [[ -z "$(git diff $PREV_COMMIT -- docker/base.Dockerfile)" ]]; then + echo "No changes detected in docker/base.Dockerfile. The base image will not be built." + echo "##vso[task.setvariable variable=BASE_CHANGED]false" + fi + + if [[ -z "$(git diff $PREV_COMMIT -- docker/venv.Dockerfile Pipfile*)" ]]; then + echo "No changes detected in docker/venv.Dockerfile or the Pipfiles. The venv image will not be built." + echo "##vso[task.setvariable variable=VENV_CHANGED]false" + fi + else + echo "No previous commit was retrieved. Either the previous build is too old and was deleted or the branch was empty before this build. All images will be built." + fi + displayName: 'Check Changed Files' + + - script: docker build -t pythondiscord/snekbox-base:latest -f docker/base.Dockerfile . + displayName: 'Build Base Image' + condition: and(succeeded(), eq(variables.BASE_CHANGED, 'true')) + + - script: docker build -t pythondiscord/snekbox-venv:latest -f docker/venv.Dockerfile . + displayName: 'Build Virtual Environment Image' + condition: and(succeeded(), or(eq(variables.BASE_CHANGED, 'true'), eq(variables.VENV_CHANGED, 'true'))) + - script: docker build -t pythondiscord/snekbox:latest -f docker/Dockerfile . displayName: 'Build Final Image' + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) + + - script: docker push pythondiscord/snekbox-base:latest + displayName: 'Push Base Image to Dockerhub' + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables.BASE_CHANGED, 'true')) + + - script: docker push pythondiscord/snekbox-venv:latest + displayName: 'Push Virtual Environment Image to Dockerhub' + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), or(eq(variables.BASE_CHANGED, 'true'), eq(variables.VENV_CHANGED, 'true'))) - script: docker push pythondiscord/snekbox:latest - displayName: 'Push Image to Dockerhub' + displayName: 'Push Final Image to Dockerhub' + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest')) diff --git a/binaries/nsjail2.5-alpine-x86_64 b/binaries/nsjail2.5-alpine-x86_64 Binary files differdeleted file mode 100644 index 9af91fc..0000000 --- a/binaries/nsjail2.5-alpine-x86_64 +++ /dev/null diff --git a/binaries/nsjail2.6-ubuntu-x86_64 b/binaries/nsjail2.6-ubuntu-x86_64 Binary files differdeleted file mode 100644 index d8df21b..0000000 --- a/binaries/nsjail2.6-ubuntu-x86_64 +++ /dev/null diff --git a/config.py b/config.py deleted file mode 100644 index 5ca23bb..0000000 --- a/config.py +++ /dev/null @@ -1,34 +0,0 @@ -import os - -import docker -from docker.errors import NotFound - - -def autodiscover(): - """Search for the snekbox container and return its IPv4 address.""" - container_names = ["rmq", "pdrmq", "snekbox_pdrmq_1"] - - client = docker.from_env() - for name in container_names: - try: - container = client.containers.get(name) - if container.status == "running": - host = list(container.attrs.get('NetworkSettings').get('Networks').values()) - host = host[0]['IPAddress'] - return host - except NotFound: - continue - except Exception: - pass - - return '127.0.0.1' - - -USERNAME = os.environ.get('RMQ_USERNAME', 'guest') -PASSWORD = os.environ.get('RMQ_PASSWORD', 'guest') -HOST = os.environ.get('RMQ_HOST', autodiscover()) -PORT = 5672 -QUEUE = 'input' -EXCHANGE = QUEUE -ROUTING_KEY = QUEUE -EXCHANGE_TYPE = 'direct' diff --git a/docker-compose.yml b/docker-compose.yml index 3aedf14..1fe8e39 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,43 +1,8 @@ -version: '3' +version: "3.7" services: - pdrmq: - hostname: "pdrmq" - image: pythondiscord/rmq:latest - expose: - - "15672" - ports: - - "15672:15672" - networks: - - sneknet - environment: - RABBITMQ_DEFAULT_USER: guest - RABBITMQ_DEFAULT_PASS: guest - pdsnk: - privileged: true hostname: "pdsnk" + privileged: true image: pythondiscord/snekbox:latest - networks: - - sneknet - environment: - RMQ_HOST: pdrmq - RMQ_USERNAME: guest - RMQ_PASSWORD: guest - - pdsnkweb: - hostname: "pdsnkweb" - image: pythondiscord/snekboxweb:latest - networks: - - sneknet - ports: - - "5000:5000" - expose: - - "5000" - environment: - RMQ_HOST: pdrmq - RMQ_USERNAME: guest - RMQ_PASSWORD: guest - - -networks: - sneknet: + network_mode: "host" + init: true diff --git a/docker/Dockerfile b/docker/Dockerfile index e8fa8a5..5ef8a88 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,10 +1,7 @@ -FROM pythondiscord/snekbox-base:latest +FROM pythondiscord/snekbox-venv:latest + +ENTRYPOINT ["pipenv", "run"] +CMD ["snekbox"] -RUN mkdir -p /snekbox COPY . /snekbox WORKDIR /snekbox - -RUN pipenv --rm -RUN pipenv sync - -CMD ["pipenv", "run", "snekbox"] diff --git a/docker/Dockerfile.webapp b/docker/Dockerfile.webapp deleted file mode 100644 index 988926d..0000000 --- a/docker/Dockerfile.webapp +++ /dev/null @@ -1,25 +0,0 @@ -FROM python:3.6.6-alpine3.7 - -RUN apk add --update tini -RUN apk add --update build-base - -ENV PIPENV_VENV_IN_PROJECT=1 -ENV PIPENV_IGNORE_VIRTUALENVS=1 -ENV PIPENV_NOSPIN=1 -ENV PIPENV_HIDE_EMOJIS=1 -ENV PYTHONPATH=/webapp - -RUN pip install pipenv - -RUN mkdir -p /webapp -COPY Pipfile /webapp -COPY Pipfile.lock /webapp -COPY . /webapp -WORKDIR /webapp - -RUN pipenv sync --dev - -EXPOSE 5000 - -ENTRYPOINT ["/sbin/tini", "--"] -CMD ["pipenv", "run", "snekweb"] diff --git a/docker/base.Dockerfile b/docker/base.Dockerfile index cdbd98e..19fc1b8 100644 --- a/docker/base.Dockerfile +++ b/docker/base.Dockerfile @@ -1,23 +1,25 @@ -FROM python:3.6.6-alpine3.7 - -RUN apk add --no-cache libstdc++ protobuf -RUN apk add --update build-base - -ENV PIPENV_VENV_IN_PROJECT=1 -ENV PIPENV_IGNORE_VIRTUALENVS=1 -ENV PIPENV_NOSPIN=1 -ENV PIPENV_HIDE_EMOJIS=1 -ENV PYTHONPATH=/snekbox +FROM alpine:3.9.2 as builder +RUN apk add --no-cache --update \ + bison \ + bsd-compat-headers \ + flex \ + g++ \ + gcc \ + git \ + libnl3-dev \ + linux-headers \ + make \ + protobuf-dev +RUN git clone --depth=1 https://github.com/google/nsjail.git /nsjail +WORKDIR /nsjail +RUN make +FROM python:3.7.3-alpine3.9 +ENV PIP_NO_CACHE_DIR=false +RUN apk add --no-cache --update \ + libnl3 \ + libstdc++ \ + protobuf RUN pip install pipenv - -RUN mkdir -p /snekbox -COPY Pipfile /snekbox -COPY Pipfile.lock /snekbox -COPY . /snekbox -WORKDIR /snekbox - -RUN pipenv sync --dev - -RUN cp binaries/nsjail2.5-alpine-x86_64 /usr/sbin/nsjail +COPY --from=builder /nsjail/nsjail /usr/sbin/ RUN chmod +x /usr/sbin/nsjail diff --git a/docker/venv.Dockerfile b/docker/venv.Dockerfile new file mode 100644 index 0000000..61aba58 --- /dev/null +++ b/docker/venv.Dockerfile @@ -0,0 +1,12 @@ +FROM pythondiscord/snekbox-base:latest + +ENV PIP_NO_CACHE_DIR=false \ + PIPENV_DONT_USE_PYENV=1 \ + PIPENV_HIDE_EMOJIS=1 \ + PIPENV_NOSPIN=1 \ + PIPENV_VENV_IN_PROJECT=1 + +COPY Pipfile Pipfile.lock /snekbox/ +WORKDIR /snekbox + +RUN pipenv sync @@ -1,111 +0,0 @@ -import time -import traceback - -import pika -from pika.exceptions import ConnectionClosed - -from config import EXCHANGE, EXCHANGE_TYPE, HOST, PASSWORD, PORT, QUEUE, ROUTING_KEY, USERNAME -from logs import log - - -class Rmq: - """Rabbit MQ (RMQ) implementation used for communication with the bot.""" - - def __init__(self): - self.credentials = pika.PlainCredentials(USERNAME, PASSWORD) - self.con_params = pika.ConnectionParameters(HOST, PORT, '/', self.credentials) - self.properties = pika.BasicProperties(content_type='text/plain', delivery_mode=1) - - def _declare(self, channel, queue): - channel.queue_declare( - queue=queue, - durable=False, # Do not commit messages to disk - arguments={'x-message-ttl': 5000}, # Delete message automatically after x milliseconds - auto_delete=True) # Delete queue when all connection are closed - - def consume(self, queue=QUEUE, callback=None, thread_ws=None, run_once=False): - """Subscribe to read from a RMQ channel.""" - while True: - try: - connection = pika.BlockingConnection(self.con_params) - - try: - channel = connection.channel() - self._declare(channel, queue) - channel.basic_qos(prefetch_count=1) - - if not run_once: - channel.basic_consume( - lambda ch, method, properties, body: - callback(ch, method, properties, body, thread_ws=thread_ws), - queue=queue) - - log.info(f"Connected to host: {HOST} port: {PORT} queue: {queue}") - - if thread_ws: - if not thread_ws.closed: - thread_ws.send('{"service": "connected"}') - - if run_once: - return channel.basic_get(queue=queue) - - channel.start_consuming() - - except Exception: - exc = traceback.format_exc() - log.error(exc) - - finally: - connection.close() - - except ConnectionClosed: - if thread_ws: - if not thread_ws.closed: - log.error(f"Connection to {HOST} could not be established") - thread_ws.send('{"service": "disconnected"}') - exit(1) - - log.error(f"Connection lost, reconnecting to {HOST}") - - time.sleep(2) - - def publish(self, message, queue=QUEUE, routingkey=ROUTING_KEY, exchange=EXCHANGE): - """Open a connection to publish (write) to a RMQ channel.""" - try: - connection = pika.BlockingConnection(self.con_params) - - try: - channel = connection.channel() - - self._declare(channel, queue) - - channel.exchange_declare( - exchange=exchange, - exchange_type=EXCHANGE_TYPE) - - channel.queue_bind( - exchange=exchange, - queue=queue, - routing_key=routingkey) - - result = channel.basic_publish( - exchange=exchange, - routing_key=routingkey, - body=message, - properties=self.properties) - - if result: - return result - - else: - log.error(f"Message '{message}' not delivered") - - except ConnectionClosed: - log.error(f"Could not send message, connection to {HOST} was lost") - exit(1) - - finally: - connection.close() - - except ConnectionClosed: - log.error(f"Could not connect to {HOST}") diff --git a/logs.py b/snekbox/__init__.py index fc6070e..fc6070e 100644 --- a/logs.py +++ b/snekbox/__init__.py diff --git a/snekbox.py b/snekbox/nsjail.py index 7491672..5c7d0f0 100644 --- a/snekbox.py +++ b/snekbox/nsjail.py @@ -1,18 +1,14 @@ -import json -import multiprocessing import os import subprocess import sys -from rmq import Rmq - -class Snekbox: - """Core snekbox functionality, providing safe execution of Python code.""" +class NsJail: + """Core Snekbox functionality, providing safe execution of Python code.""" def __init__(self, nsjail_binary='nsjail', - python_binary=os.path.dirname(sys.executable) + os.sep + 'python3.6'): + python_binary=os.path.dirname(sys.executable) + os.sep + 'python3.7'): self.nsjail_binary = nsjail_binary self.python_binary = python_binary self._nsjail_workaround() @@ -23,8 +19,8 @@ class Snekbox: 'sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' ), 'LANG': 'en_US.UTF-8', - 'PYTHON_VERSION': '3.6.5', - 'PYTHON_PIP_VERSION': '10.0.1', + 'PYTHON_VERSION': '3.7.3', + 'PYTHON_PIP_VERSION': '19.0.3', 'PYTHONDONTWRITEBYTECODE': '1', } @@ -96,45 +92,3 @@ class Snekbox: return 'unknown error, no error code' return output - - def execute(self, body): - """ - Handles execution of a raw JSON-formatted RMQ message, contained in ``body``. - - The message metadata, including the Python code to be executed, is - extracted from the message body. The code is then executed in the - isolated environment, and the results of the execution published - to RMQ. Once published, the system exits, since the snekboxes - are created and disposed of per-execution. - """ - msg = body.decode('utf-8') - result = '' - snek_msg = json.loads(msg) - snekid = snek_msg['snekid'] - snekcode = snek_msg['message'].strip() - - result = self.python3(snekcode) - - rmq.publish(result, - queue=snekid, - routingkey=snekid, - exchange=snekid) - exit(0) - - def message_handler(self, ch, method, properties, body, thread_ws=None): - """Spawns a daemon process that handles RMQ messages.""" - p = multiprocessing.Process(target=self.execute, args=(body,)) - p.daemon = True - p.start() - - ch.basic_ack(delivery_tag=method.delivery_tag) - - -if __name__ == '__main__': - try: - rmq = Rmq() - snkbx = Snekbox() - rmq.consume(callback=snkbx.message_handler) - except KeyboardInterrupt: - print('Exited') - exit(0) diff --git a/snekbox/site/snekapp.py b/snekbox/site/snekapp.py new file mode 100644 index 0000000..ef96148 --- /dev/null +++ b/snekbox/site/snekapp.py @@ -0,0 +1,35 @@ +from flask import Flask, jsonify, render_template, request + +from snekbox.nsjail import NsJail + +nsjail = NsJail() + +# Load app +app = Flask(__name__) +app.use_reloader = False + +# Logging +log = app.logger + + [email protected]('/') +def index(): + """Return a page with a form for inputting code to be executed.""" + return render_template('index.html') + + [email protected]('/result', methods=["POST", "GET"]) +def result(): + """Execute code and return a page displaying the results.""" + if request.method == "POST": + code = request.form["Code"] + output = nsjail.python3(code) + return render_template('result.html', code=code, result=output) + + [email protected]('/input', methods=["POST"]) +def code_input(): + """Execute code and return the results.""" + body = request.get_json() + output = nsjail.python3(body["code"]) + return jsonify(input=body["code"], output=output) diff --git a/snekbox/site/templates/index.html b/snekbox/site/templates/index.html new file mode 100644 index 0000000..41980d1 --- /dev/null +++ b/snekbox/site/templates/index.html @@ -0,0 +1,14 @@ +<!DOCTYPE html> +<html> + <meta charset="utf-8" /> + <title>snekboxweb</title> + <body> + <form action="/result" method="POST"> + <p>Code:<br><textarea name="Code" rows="20" cols="80"> +def sum(a,b): + return a+b +print( sum(1,2) )</textarea></p> + <p><input type="submit" value="Run"></p> + </form> + </body> +</html> diff --git a/snekbox/site/templates/result.html b/snekbox/site/templates/result.html new file mode 100644 index 0000000..e339605 --- /dev/null +++ b/snekbox/site/templates/result.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> + <meta charset="utf-8" /> + <title>snekboxweb</title> + <body> + <p>Code Evaluated:<br><pre>{{ code }}</pre></p> + <p>Results:<br><pre>{{ result }}</pre></p> + </body> +</html> diff --git a/snekweb.py b/snekweb.py deleted file mode 100644 index 3e20fda..0000000 --- a/snekweb.py +++ /dev/null @@ -1,72 +0,0 @@ -import json -import logging -import threading -import traceback - -from flask import Flask, render_template -from flask_sockets import Sockets -from rmq import Rmq - -# Load app -app = Flask(__name__) -app.jinja_env.auto_reload = True -sockets = Sockets(app) - -# Logging -gunicorn_logger = logging.getLogger('gunicorn.error') -app.logger.handlers = gunicorn_logger.handlers -app.logger.setLevel(gunicorn_logger.level) -log = app.logger - - [email protected]('/') -def index(): - """Root path returns standard index.html.""" - return render_template('index.html') - - [email protected]('/ws/<snekboxid>') -def websocket_route(ws, snekboxid): - """Opens a websocket that spawns and connects to a snekbox daemon.""" - localdata = threading.local() - localdata.thread_ws = ws - - rmq = Rmq() - - def message_handler(ch, method, properties, body, thread_ws): - msg = body.decode('utf-8') - thread_ws.send(msg) - ch.basic_ack(delivery_tag=method.delivery_tag) - - consumer_parameters = {'queue': snekboxid, - 'callback': message_handler, - 'thread_ws': localdata.thread_ws} - - consumer = threading.Thread( - target=rmq.consume, - kwargs=consumer_parameters) - - consumer.daemon = True - consumer.start() - - try: - while not ws.closed: - message = ws.receive() - if message: - snek_msg = json.dumps({"snekid": snekboxid, "message": message}) - log.info(f"User {snekboxid} sends message\n{message.strip()}") - rmq.publish(snek_msg) - - except Exception: - log.info(traceback.format_exc()) - - finally: - if not ws.closed: - ws.close() - - -if __name__ == '__main__': - from gevent import pywsgi - from geventwebsocket.handler import WebSocketHandler - server = pywsgi.WSGIServer(('0.0.0.0', 5000), app, handler_class=WebSocketHandler) - server.serve_forever() diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 8de9627..0000000 --- a/templates/index.html +++ /dev/null @@ -1,106 +0,0 @@ -<!DOCTYPE html> -<meta charset="utf-8" /> -<title>snekboxweb</title> -<script language="javascript" type="text/javascript"> - - -let _ready = false -let snekbox_id -var output; - -snekbox_id = sessionStorage.getItem("snekbox_id"); -console.log(snekbox_id) -if (snekbox_id == null) { - snekbox_id = generate_id() - sessionStorage.setItem("snekbox_id", snekbox_id) - console.log(snekbox_id) -} - -function init(){ - output = document.getElementById("output"); - websocketHandler(); -} - -function websocketHandler(){ - var here = window.location.host; - var wsUri = `ws://${here}/ws/`+snekbox_id; - websocket = new WebSocket(wsUri); - websocket.onopen = function(evt) { onOpen(evt) }; - websocket.onclose = function(evt) { onClose(evt) }; - websocket.onmessage = function(evt) { onMessage(evt) }; - websocket.onerror = function(evt) { onError(evt) }; -} - -function onOpen(evt){ - _ready = true - console.log("CONNECTED"); -} - -function onClose(evt){ - _ready = false - console.log("DISCONNECTED"); -} - -function onMessage(evt){ - writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>'); -} - -function exit(){ - websocket.close(); -} - -function onError(evt){ - _ready = false - writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data); -} - -function sendMessage(msg){ - waitForSocketConnection(function(){ - websocket.send(msg); - }); - console.log("sent message "+msg) -} - -function waitForSocketConnection(callback){ - setTimeout( - function () { - if (_ready === true) { - if(callback != null){ - callback();} - return; - } - else { - waitForSocketConnection(callback);} - - }, 500); // milliseconds -} - -function writeToScreen(message){ - var pre = document.createElement("p"); - pre.style.wordWrap = "break-word"; - pre.innerHTML = message; - output.appendChild(pre); -} - -function sendFromInput(){ - var msg = document.getElementById("field1").value; - sendMessage(msg) -} - -function generate_id(){ - return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); -} - -window.addEventListener("load", init, false); - -</script> - -<textarea rows="4" cols="50" type="text" id="field1"> -def sum(a,b): - return a+b -print( sum(1,2) ) -</textarea> -<br> -<button onclick="sendFromInput()">Send</button> -<button onclick="exit()">disconnect from websocket</button> -<div id="output"></div> diff --git a/tests/test_snekbox.py b/tests/test_snekbox.py index e2505d6..c08178f 100644 --- a/tests/test_snekbox.py +++ b/tests/test_snekbox.py @@ -1,43 +1,39 @@ import unittest -import pytest -import os -import json -from snekbox import Snekbox -from rmq import Rmq +from snekbox.nsjail import NsJail -r = Rmq() - -snek = Snekbox() +nsjail = NsJail() class SnekTests(unittest.TestCase): def test_nsjail(self): - result = snek.python3('print("test")') + result = nsjail.python3('print("test")') self.assertEquals(result.strip(), 'test') # def test_memory_error(self): # code = ('x = "*"\n' # 'while True:\n' # ' x = x * 99\n') - # result = snek.python3(code) + # result = nsjail.python3(code) # self.assertEquals(result.strip(), 'timed out or memory limit exceeded') def test_timeout(self): - code = ('x = "*"\n' - 'while True:\n' - ' try:\n' - ' x = x * 99\n' - ' except:\n' - ' continue\n') - - result = snek.python3(code) + code = ( + 'x = "*"\n' + 'while True:\n' + ' try:\n' + ' x = x * 99\n' + ' except:\n' + ' continue\n' + ) + + result = nsjail.python3(code) self.assertEquals(result.strip(), 'timed out or memory limit exceeded') def test_kill(self): code = ('import subprocess\n' 'print(subprocess.check_output("kill -9 6", shell=True).decode())') - result = snek.python3(code) + result = nsjail.python3(code) if 'ModuleNotFoundError' in result.strip(): self.assertIn('ModuleNotFoundError', result.strip()) else: @@ -47,7 +43,7 @@ class SnekTests(unittest.TestCase): code = ('import os\n' 'while 1:\n' ' os.fork()') - result = snek.python3(code) + result = nsjail.python3(code) self.assertIn('Resource temporarily unavailable', result.strip()) def test_juan_golf(self): # in honour of Juan @@ -56,5 +52,5 @@ class SnekTests(unittest.TestCase): "bytecode = CodeType(0,1,0,0,0,b'',(),(),(),'','',1,b'')\n" "exec(bytecode)") - result = snek.python3(code) + result = nsjail.python3(code) self.assertEquals('unknown error, code: 111', result.strip()) |