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_64Binary files differ deleted 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_64Binary files differ deleted 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()) | 
