From 1cd8eee616f484733018f87007aeccd814edba6b Mon Sep 17 00:00:00 2001 From: Ahmad Bilal Khalid Date: Mon, 24 Jun 2019 15:46:06 +0500 Subject: [PATCH] v1 code --- .gitignore | 4 ++ Pipfile | 17 +++++ Pipfile.lock | 184 +++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 33 +++++++++ helper.py | 45 +++++++++++++ main.py | 43 ++++++++++++ 6 files changed, 326 insertions(+) create mode 100644 .gitignore create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 README.md create mode 100644 helper.py create mode 100644 main.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..514dde7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.env +.idea/ +__pycache__/ +venv/ \ No newline at end of file diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..a5b5d2e --- /dev/null +++ b/Pipfile @@ -0,0 +1,17 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +pyotp = "*" +python-decouple = "*" +requests = "*" +flask = "*" +flask-restful = "*" +python-etcd = "*" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..08dcb84 --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,184 @@ +{ + "_meta": { + "hash": { + "sha256": "edc89a3dee9f14e3a59020136d4d87c006baab717f9f67b61e4b197e6a29865b" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "aniso8601": { + "hashes": [ + "sha256:513d2b6637b7853806ae79ffaca6f3e8754bdd547048f5ccc1420aec4b714f1e", + "sha256:d10a4bf949f619f719b227ef5386e31f49a2b6d453004b21f02661ccc8670c7b" + ], + "version": "==7.0.0" + }, + "certifi": { + "hashes": [ + "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939", + "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695" + ], + "version": "==2019.6.16" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "version": "==7.0" + }, + "dnspython": { + "hashes": [ + "sha256:36c5e8e38d4369a08b6780b7f27d790a292b2b08eea01607865bf0936c558e01", + "sha256:f69c21288a962f4da86e56c4905b49d11aba7938d3d740e80d9e366ee4f1632d" + ], + "version": "==1.16.0" + }, + "flask": { + "hashes": [ + "sha256:ad7c6d841e64296b962296c2c2dabc6543752985727af86a975072dea984b6f3", + "sha256:e7d32475d1de5facaa55e3958bc4ec66d3762076b074296aa50ef8fdc5b9df61" + ], + "index": "pypi", + "version": "==1.0.3" + }, + "flask-restful": { + "hashes": [ + "sha256:ecd620c5cc29f663627f99e04f17d1f16d095c83dc1d618426e2ad68b03092f8", + "sha256:f8240ec12349afe8df1db168ea7c336c4e5b0271a36982bff7394f93275f2ca9" + ], + "index": "pypi", + "version": "==0.3.7" + }, + "idna": { + "hashes": [ + "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", + "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + ], + "version": "==2.8" + }, + "itsdangerous": { + "hashes": [ + "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19", + "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749" + ], + "version": "==1.1.0" + }, + "jinja2": { + "hashes": [ + "sha256:065c4f02ebe7f7cf559e49ee5a95fb800a9e4528727aec6f24402a5374c65013", + "sha256:14dd6caf1527abb21f08f86c784eac40853ba93edb79552aa1e4b8aef1b61c7b" + ], + "version": "==2.10.1" + }, + "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" + }, + "pyotp": { + "hashes": [ + "sha256:1e3dc3d16919c4efac528d1dbecc17de1a97c4ecfdacb89d7726ed2c6645adff", + "sha256:be0ffeabddaa5ee53e7204e7740da842d070cf69168247a3d0c08541b84de602" + ], + "index": "pypi", + "version": "==2.2.7" + }, + "python-decouple": { + "hashes": [ + "sha256:1317df14b43efee4337a4aa02914bf004f010cd56d6c4bd894e6474ec8c4fe2d" + ], + "index": "pypi", + "version": "==3.1" + }, + "python-etcd": { + "hashes": [ + "sha256:f1b5ebb825a3e8190494f5ce1509fde9069f2754838ed90402a8c11e1f52b8cb" + ], + "index": "pypi", + "version": "==0.4.5" + }, + "pytz": { + "hashes": [ + "sha256:303879e36b721603cc54604edcac9d20401bdbe31e1e4fdee5b9f98d5d31dfda", + "sha256:d747dd3d23d77ef44c6a3526e274af6efeb0a6f1afd5a69ba4d5be4098c8e141" + ], + "version": "==2019.1" + }, + "requests": { + "hashes": [ + "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", + "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + ], + "index": "pypi", + "version": "==2.22.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "urllib3": { + "hashes": [ + "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1", + "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232" + ], + "version": "==1.25.3" + }, + "werkzeug": { + "hashes": [ + "sha256:865856ebb55c4dcd0630cdd8f3331a1847a819dda7e8c750d3db6f2aa6c0209c", + "sha256:a0b915f0815982fb2a09161cb8f31708052d0951c3ba433ccc5e1aa276507ca6" + ], + "version": "==0.15.4" + } + }, + "develop": {} +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..7377d10 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# ucloud-api +[![Project Status: WIP – Initial development is in progress, but there has not yet been a stable, usable release suitable for the public.](https://www.repostatus.org/badges/latest/wip.svg)](https://www.repostatus.org/#wip) + +## Installation + +**Make sure you have Python >= 3.6 and Pipenv installed.** + +1. Clone the repository and `cd` into it. +2. Run the following commands + - `pipenv install` + - `pipenv shell` + - `python main.py` + +## Endpoints + +### Create VM +To Create VM send a `POST` request at `/vm/create` with JSON body. +An Example JSON Request Body is shown below + + +```json +{ + "name": "username", + "realm": "user realm", + "seed": "user seed", + "specs": { + "cpu": 16, + "ram": 256, + "hdd": "2TB", + "sdd": "256GB" + } +} +``` \ No newline at end of file diff --git a/helper.py b/helper.py new file mode 100644 index 0000000..a426ec4 --- /dev/null +++ b/helper.py @@ -0,0 +1,45 @@ +import binascii +import requests + +from decouple import config +from pyotp import TOTP + + +def check_otp(name, realm, seed): + try: + data = { + "auth_name": config('AUTH_NAME', ''), + "auth_token": TOTP(config('AUTH_SEED', '')).now(), + "auth_realm": config('AUTH_REALM', ''), + "name": name, + "realm": realm, + "token": TOTP(seed).now() + } + except binascii.Error: + return 400 + + response = requests.post( + "{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format( + OTP_SERVER=config('OTP_SERVER', ''), + OTP_VERIFY_ENDPOINT=config('OTP_VERIFY_ENDPOINT', 'verify/') + ), + data=data + ) + return response.status_code + + +def get_next_id(client, path): + r = client.read(path) + + max_key_result = max(r.children, key=lambda x: int(strip_nondigit(x.key))) + if max_key_result is None: + # No key found + return 0 + + max_key = strip_nondigit(max_key_result.key.split("/")[-1]) # Get the last portion of key + + return int(max_key) + 1 + + +def strip_nondigit(s): + return "".join([char for char in s if char.isdigit()]) diff --git a/main.py b/main.py new file mode 100644 index 0000000..908392b --- /dev/null +++ b/main.py @@ -0,0 +1,43 @@ +import etcd + +from helper import check_otp, get_next_id +from flask import Flask +from flask_restful import Resource, Api, reqparse +from decouple import config + +app = Flask(__name__) +api = Api(app) + +etcd_client = etcd.Client(host=config("ETCD_HOST"), port=int(config("ETCD_PORT"))) + +createvm_argparser = reqparse.RequestParser() +createvm_argparser.add_argument("name", type=str, required=True) +createvm_argparser.add_argument("realm", type=str, required=True) +createvm_argparser.add_argument("seed", type=str, required=True) +createvm_argparser.add_argument("specs", type=dict, required=True) + + +class CreateVM(Resource): + def post(self): + createvm_args = createvm_argparser.parse_args() + name, realm, seed, specs = createvm_args.name, createvm_args.realm,\ + createvm_args.seed, createvm_args.specs + + if check_otp(name, realm, seed) == 200: + # User is good + next_vm_id = get_next_id(etcd_client, "/v1/vm/") + + vm_entry = {"owner": name, + "specs": specs} + + etcd_client.write(f"/v1/vm/{next_vm_id}", vm_entry) + + return {'message': "VM Created"}, 200 + else: + return {'message': 'Invalid Credentials'}, 400 + + +api.add_resource(CreateVM, '/vm/create') + +if __name__ == '__main__': + app.run(debug=True)