From 9cddb549af9ab9c33edac67d150d9e682c12cdce Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Fri, 16 Dec 2022 13:50:34 +0100 Subject: [PATCH 01/37] .codeclimate, .editorconfig, .gitignore --- .codeclimate.yml | 7 +++++++ .editorconfig | 28 ++++++++++++++++++++++++++++ .gitignore | 3 +++ 3 files changed, 38 insertions(+) create mode 100644 .codeclimate.yml create mode 100644 .editorconfig create mode 100644 .gitignore diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..09c810c --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,7 @@ +plugins: + bandit: + enabled: true + sonar-python: + enabled: true + pylint: + enabled: true diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b35140d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,28 @@ +root = true + +[*] +insert_final_newline = true + +# Matches multiple files with brace expansion notation +# Set default charset +[*.{js,py}] +charset = utf-8 + +# 4 space indentation +[*.py] +indent_style = space +indent_size = 4 + +# Tab indentation (no size specified) +[Makefile] +indent_style = tab + +# Indentation override for all JS under lib directory +[lib/**.js] +indent_style = space +indent_size = 2 + +# Matches the exact files either package.json or .travis.yml +[{package.json,.travis.yml}] +indent_style = space +indent_size = 2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1cdf68f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.DS_Store +venv/ +.idea/ From bc679be0aa65cec83d7e8e0412e846e457e00e56 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Fri, 16 Dec 2022 13:51:00 +0100 Subject: [PATCH 02/37] README.md, ToDo.md and added some docs --- README.md | 5 + ToDo.md | 9 ++ doc/Reverse Engineering Notes.md | 169 +++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 ToDo.md create mode 100644 doc/Reverse Engineering Notes.md diff --git a/README.md b/README.md index 5861a35..6232612 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,8 @@ # FastAPI-DLS Minimal Licencing Servie. + + +## Get token file + +**The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.** diff --git a/ToDo.md b/ToDo.md new file mode 100644 index 0000000..c87b012 --- /dev/null +++ b/ToDo.md @@ -0,0 +1,9 @@ +# FastAPI-DLS Server + + +## ToDo + +- Create Client Token (`.tok`-file) +- Docker Image +- Create Certificates if not exist +- Keep track of clients and Leases diff --git a/doc/Reverse Engineering Notes.md b/doc/Reverse Engineering Notes.md new file mode 100644 index 0000000..c91b94b --- /dev/null +++ b/doc/Reverse Engineering Notes.md @@ -0,0 +1,169 @@ +# Reverse Engineering Notes + +# Usefully commands + +## Check licensing status + +- `nvidia-smi -q | grep "License"` + +**Output** + +``` +vGPU Software Licensed Product + License Status : Licensed (Expiry: 2023-1-14 12:59:52 GMT) +``` + +## Track licensing progress + +- NVIDIA Grid Log: `journalctl -u nvidia-gridd -f` + +``` +systemd[1]: Started NVIDIA Grid Daemon. +nvidia-gridd[2986]: Configuration parameter ( ServerAddress ) not set +nvidia-gridd[2986]: vGPU Software package (0) +nvidia-gridd[2986]: Ignore service provider and node-locked licensing +nvidia-gridd[2986]: NLS initialized +nvidia-gridd[2986]: Acquiring license. (Info: license.nvidia.space; NVIDIA RTX Virtual Workstation) +nvidia-gridd[2986]: License acquired successfully. (Info: license.nvidia.space, NVIDIA RTX Virtual Workstation; Expiry: 2023-1-29 22:3:0 GMT) +``` + +# DLS-Container File-System (Docker) + +## Configuration data + +Most variables and configs are stored in `/var/lib/docker/volumes/configurations/_data`. + +## Dive / Docker image inspector + +- `dive dls:appliance` + +The source code is stored in `/venv/lib/python3.9/site-packages/nls_*`. + +Image-Reference: + +``` +Tags: (unavailable) +Id: d1c7976a5d2b3681ff6c5a30f8187e4015187a83f3f285ba4a37a45458bd6b98 +Digest: sha256:311223c5af7a298ec1104f5dc8c3019bfb0e1f77256dc3d995244ffb295a97 +1f +Command: +#(nop) ADD file:c1900d3e3a29c29a743a8da86c437006ec5d2aa873fb24e48033b6bf492bb37b in / +``` + +## Private Key (Site-Key) + +- `/etc/dls/config/decryptor/decryptor` + +``` +-----BEGIN RSA PRIVATE KEY----- +... +-----END RSA PRIVATE KEY----- +``` + +## Site Key Uri - `/etc/dls/config/site_key_uri.bin` + +``` +base64-content... +``` + +## DB Password - `/etc/dls/config/dls_db_password.bin` + +``` +base64-content... +``` + +**Decrypt database password** + +``` +cat /etc/dls/config/dls_db_password.bin | base64 -d > /etc/dls/config/dls_db_password.bin.raw +openssl rsautl -decrypt -inkey /tmp/private-key.pem -in /etc/dls/config/dls_db_password.bin.raw +``` + +# Database + +- It's enough to manipulate database licenses. There must not be changed any line of code to bypass licensing + validations. + +# Logging / Stack Trace + +- https://docs.nvidia.com/license-system/latest/nvidia-license-system-user-guide/index.html#troubleshooting-dls-instance + +**Failed licensing log** + +``` +{ + "activity": 100, + "context": { + "SERVICE_INSTANCE_ID": "b43d6e46-d6d0-4943-8b8d-c66a5f6e0d38", + "SERVICE_INSTANCE_NAME": "DEFAULT_2022-12-14_12:48:30", + "description": "borrow failed: NotFoundError(no pool features found for: NVIDIA RTX Virtual Workstation)", + "event_type": null, + "function_name": "_evt", + "lineno": 54, + "module_name": "nls_dal_lease_dls.event", + "operation_id": "e72a8ca7-34cc-4e11-b80c-273592085a24", + "origin_ref": "3f7f5a50-a26b-425b-8d5e-157f63e72b1c", + "service_name": "nls_services_lease" + }, + "detail": { + "oc": { + "license_allotment_xid": "10c4317f-7c4c-11ed-a524-0e4252a7e5f1", + "origin_ref": "3f7f5a50-a26b-425b-8d5e-157f63e72b1c", + "service_instance_xid": "b43d6e46-d6d0-4943-8b8d-c66a5f6e0d38" + }, + "operation_id": "e72a8ca7-34cc-4e11-b80c-273592085a24" + }, + "id": "0cc9e092-3b92-4652-8d9e-7622ef85dc79", + "metadata": {}, + "ts": "2022-12-15T10:25:36.827661Z" +} + +{ + "activity": 400, + "context": { + "SERVICE_INSTANCE_ID": "b43d6e46-d6d0-4943-8b8d-c66a5f6e0d38", + "SERVICE_INSTANCE_NAME": "DEFAULT_2022-12-14_12:48:30", + "description": "lease_multi_create failed: no pool features found for: NVIDIA RTX Virtual Workstation", + "event_by": "system", + "function_name": "lease_multi_create", + "level": "warning", + "lineno": 157, + "module_name": "nls_services_lease.controllers.lease_multi_controller", + "operation_id": "e72a8ca7-34cc-4e11-b80c-273592085a24", + "service_name": "nls_services_lease" + }, + "detail": { + "_msg": "lease_multi_create failed: no pool features found for: NVIDIA RTX Virtual Workstation", + "exec_info": ["NotFoundError", "NotFoundError(no pool features found for: NVIDIA RTX Virtual Workstation)", " File \"/venv/lib/python3.9/site-packages/nls_services_lease/controllers/lease_multi_controller.py\", line 127, in lease_multi_create\n data = _leaseMulti.lease_multi_create(event_args)\n File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 208, in lease_multi_create\n raise e\n File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 184, in lease_multi_create\n self._try_proposals(oc, mlr, results, detail)\n File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 219, in _try_proposals\n lease = self._leases.create(creator)\n File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 230, in create\n features = self._get_features(creator)\n File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 148, in _get_features\n self._explain_not_available(cur, creator)\n File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 299, in _explain_not_available\n raise NotFoundError(f'no pool features found for: {lcc.product_name}')\n"], + "operation_id": "e72a8ca7-34cc-4e11-b80c-273592085a24" + }, + "id": "282801b9-d612-40a5-9145-b56d8e420dac", + "metadata": {}, + "ts": "2022-12-15T10:25:36.831673Z" +} + +``` + +**Stack Trace** + +``` +"NotFoundError", "NotFoundError(no pool features found for: NVIDIA RTX Virtual Workstation)", " File \"/venv/lib/python3.9/site-packages/nls_services_lease/controllers/lease_multi_controller.py\", line 127, in lease_multi_create + data = _leaseMulti.lease_multi_create(event_args) + File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 208, in lease_multi_create + raise e + File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 184, in lease_multi_create + self._try_proposals(oc, mlr, results, detail) + File \"/venv/lib/python3.9/site-packages/nls_core_lease/lease_multi.py\", line 219, in _try_proposals + lease = self._leases.create(creator) + File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 230, in create + features = self._get_features(creator) + File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 148, in _get_features + self._explain_not_available(cur, creator) + File \"/venv/lib/python3.9/site-packages/nls_dal_lease_dls/leases.py\", line 299, in _explain_not_available + raise NotFoundError(f'no pool features found for: {lcc.product_name}') +" +``` + +# Nginx + +- NGINX uses `/opt/certs/cert.pem` and `/opt/certs/key.pem` From b5cade6a95f930bfa3734340ceaaddfa30897ef0 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Fri, 16 Dec 2022 13:51:14 +0100 Subject: [PATCH 03/37] main.app --- app/helper.py | 28 ++++++ app/main.py | 264 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 app/helper.py create mode 100644 app/main.py diff --git a/app/helper.py b/app/helper.py new file mode 100644 index 0000000..6684de6 --- /dev/null +++ b/app/helper.py @@ -0,0 +1,28 @@ +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKeyWithSerialization +from cryptography.hazmat.primitives.serialization import load_pem_private_key, Encoding, PrivateFormat, PublicFormat, \ + NoEncryption + + +def load_file(filename) -> bytes: + with open(filename, 'rb') as file: + content = file.read() + return content + + +def load_key(filename) -> RSAPrivateKeyWithSerialization: + return load_pem_private_key(data=load_file(filename), password=None) + + +def private_bytes(rsa: RSAPrivateKeyWithSerialization) -> bytes: + return rsa.private_bytes( + encoding=Encoding.PEM, + format=PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=NoEncryption() + ) + + +def public_key(rsa: RSAPrivateKeyWithSerialization) -> bytes: + return rsa.public_key().public_bytes( + encoding=Encoding.PEM, + format=PublicFormat.SubjectPublicKeyInfo + ) diff --git a/app/main.py b/app/main.py new file mode 100644 index 0000000..71007c5 --- /dev/null +++ b/app/main.py @@ -0,0 +1,264 @@ +from base64 import b64encode +from hashlib import sha256 +from uuid import uuid4 + +from fastapi import FastAPI, HTTPException +from fastapi.requests import Request +import json +from datetime import datetime, timedelta +from calendar import timegm +from jose import jws, jwk, jwt +from jose.constants import ALGORITHMS +from starlette.responses import StreamingResponse + +from app.helper import load_key, private_bytes, public_key + +app = FastAPI() + +URL = '192.168.178.196' +SITE_KEY_FILE = load_key('/opt/fastapi-dls/site.key') +SITE_KEY_XID = '00000000-0000-0000-0000-000000000000' + +SITE_KEY_RSA = private_bytes(SITE_KEY_FILE) +SITE_KEY_PUB = public_key(SITE_KEY_FILE) + + +@app.get('/') +async def index(): + return {'hello': 'world'} + + +# venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py +@app.get('/client-token') +async def client_token(): + public_key_me = SITE_KEY_FILE.public_key().public_numbers() + service_instance_public_key_me = { + "mod": hex(public_key_me.n)[2:], + "exp": public_key_me.e, + }, + + cur_time = datetime.utcnow() + exp_time = cur_time + timedelta(days=1) + payload = { + "jti": str(uuid4()), + "iss": "NLS Service Instance", + "aud": "NLS Licensed Client", + "iat": cur_time, + "nbf": cur_time, + "exp": exp_time, + "update_mode": "ABSOLUTE", + "scope_ref_list": [ + "482f24b5-0a60-4ec2-a63a-9ed00bc2534e" + # todo: "scope_ref_list" should be a unique client id (which identifies leases, etc.) + ], + "fulfillment_class_ref_list": [], + "service_instance_configuration": { + "nls_service_instance_ref": "b43d6e46-d6d0-4943-8b8d-c66a5f6e0d38", + "svc_port_set_list": [ + { + "idx": 0, + "d_name": "DLS", + "svc_port_map": [ + {"service": "auth", "port": 443}, + {"service": "lease", "port": 443} + ] + } + ], + "node_url_list": [{"idx": 0, "url": URL, "url_qr": URL, "svc_port_set_idx": 0}] + }, + "service_instance_public_key_configuration": { + "service_instance_public_key_me": service_instance_public_key_me, + "service_instance_public_key_pem": SITE_KEY_PUB.decode('utf-8'), + "key_retention_mode": "LATEST_ONLY" + } + } + + key = jwk.construct(SITE_KEY_RSA, algorithm=ALGORITHMS.RS512) + data = jwt.encode(payload, key=key, headers=None, algorithm='RS256') + + response = StreamingResponse(iter([data]), media_type="text/plain") + response.headers["Content-Disposition"] = "attachment; filename=client_token.tok" + return response + + +# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py +@app.post('/auth/v1/origin') +async def auth(request: Request, status_code=201): + body = await request.body() + body = body.decode('utf-8') + j = json.loads(body) + # {"candidate_origin_ref":"00112233-4455-6677-8899-aabbccddeeff","environment":{"fingerprint":{"mac_address_list":["fa:52:16:65:c5:28"]},"hostname":"debian-grid-test","ip_address_list":["192.168.178.12","fdfe:7fcd:e30f:40f5:f852:16ff:fe65:c528","fe80::f852:16ff:fe65:c528%enp6s18"],"guest_driver_version":"510.85.02","os_platform":"Debian GNU/Linux 11 (bullseye) 11","os_version":"11 (bullseye)"},"registration_pending":false,"update_pending":false} + + cur_time = datetime.utcnow() + response = { + "origin_ref": j['candidate_origin_ref'], + "environment": { + "fingerprint": {"mac_address_list": ["e4:b9:7a:e5:7b:ff"]}, + "guest_driver_version": "guest_driver_version", + "hostname": "myhost", + "os_platform": "os_platform", + "os_version": "os_version", + "ip_address_list": ["192.168.1.129"] + }, + "svc_port_set_list": None, + "node_url_list": None, + "node_query_order": None, + "prompts": None, + "sync_timestamp": cur_time + } + return response + + +# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py +# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - CodeResponse +@app.post('/auth/v1/code') +async def code(request: Request): + body = await request.body() + body = body.decode('utf-8') + j = json.loads(body) + # {"code_challenge":"QhDaArKDQwFeQ5Jq4Dn5hy37ODF8Jq3igXCXvWEgs5I","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"} + + cur_time = datetime.utcnow() + expires = cur_time + timedelta(days=1) + + payload = { + 'iat': timegm(cur_time.timetuple()), + 'exp': timegm(expires.timetuple()), + 'challenge': j['code_challenge'], + 'origin_ref': j['code_challenge'], + 'key_ref': SITE_KEY_XID, + 'kid': SITE_KEY_XID + } + + headers = None + kid = payload.get('kid') + if kid: + headers = {'kid': kid} + key = jwk.construct(SITE_KEY_RSA, algorithm=ALGORITHMS.RS512) + auth_code = jws.sign(payload, key, headers=headers, algorithm='RS256') + + response = { + "auth_code": auth_code, + "sync_timestamp": datetime.utcnow(), + "prompts": None + } + return response + + +# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py +# venv/lib/python3.9/site-packages/nls_core_auth/auth.py - TokenResponse +@app.post('/auth/v1/token') +async def token(request: Request): + body = await request.body() + body = body.decode('utf-8') + j = json.loads(body) + # {"auth_code":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzExODI5MTQsImV4cCI6MTY3MTI2OTMxNCwiY2hhbGxlbmdlIjoiaXdZdFpIME03K0ZZUWdRQXEwbjhabThWcFpJbWdtV1NDSXI1MkdTSlMxayIsIm9yaWdpbl9yZWYiOiJpd1l0WkgwTTcrRllRZ1FBcTBuOFptOFZwWkltZ21XU0NJcjUyR1NKUzFrIiwia2V5X3JlZiI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsImtpZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCJ9.hkBPQx7UbXqwRzpTSp5fASwLg7rJOgjDOGD98Zh6pEkPW09KjxcsaHKeR8KIZmDS1S_kLed93-UzUY4wXAylFBlM-daL-TEbHJau2muZGWXPrtdsGLI9CLFcc0dmocq1_5rnRV3liqjdZwL8djK9Fx_5tOzEfeI9oCJ49Sh2LD_p1vkFcqUv9z9mVL9IGsoRM6y4hJ2YKBloijzhMLp5E7nojyD6Z8PQZ0mOIOc3tncAaXQS47JhgGsJPUDR-YoLF5uNpAlJKZP2eZWJt3P7MvhIz3lxFPUJ5jHX64Vf0Ds10-GBctZuy1-eCLBXj74uQy_U4KlnCif-5N8bPTvgxw","code_verifier":"CgnDPaugQCb4U6l3EfJSFsA/JxMqNO4TqONeb9yl8EVRWU88yTPlEeJgZQO0f/JVnScYOsvwa0jcvTAMBulEKgucfxDDVL1cBOylGugQ0QlJsXU5hJ8VLAQtOyPthnVyEutERNyOKVwl3YI5Z5EfUcfuhDqmxBUpnAFtQ9H3R3g"} + + # payload = self._security.get_valid_payload(req.auth_code) # todo + key = jwk.construct(SITE_KEY_PUB, algorithm=ALGORITHMS.RS512) + payload = jwt.decode(token=j['auth_code'], key=key) + + # validate the code challenge + if payload['challenge'] != b64encode(sha256(j['code_verifier'].encode('utf-8')).digest()).rstrip(b'=').decode( + 'utf-8'): + raise HTTPException(status_code=403, detail='expected challenge did not match verifier') + + cur_time = datetime.utcnow() + access_expires_on = cur_time + timedelta(days=1) + + new_payload = { + 'iat': timegm(cur_time.timetuple()), + 'nbf': timegm(cur_time.timetuple()), + 'iss': 'https://cls.nvidia.org', + 'aud': 'https://cls.nvidia.org', + 'exp': timegm(access_expires_on.timetuple()), + 'origin_ref': payload['origin_ref'], + 'key_ref': SITE_KEY_XID, + 'kid': SITE_KEY_XID, + } + + headers = None + kid = payload.get('kid') + if kid: + headers = {'kid': kid} + key = jwk.construct(SITE_KEY_RSA, algorithm=ALGORITHMS.RS512) + auth_token = jwt.encode(new_payload, key=key, headers=headers, algorithm='RS256') + + response = { + "expires": access_expires_on, + "auth_token": auth_token, + "sync_timestamp": cur_time, + } + + return response + + +@app.post('/leasing/v1/lessor') +async def lessor(request: Request): + body = await request.body() + body = body.decode('utf-8') + j = json.loads(body) + # {'fulfillment_context': {'fulfillment_class_ref_list': []}, 'lease_proposal_list': [{'license_type_qualifiers': {'count': 1}, 'product': {'name': 'NVIDIA RTX Virtual Workstation'}}], 'proposal_evaluation_mode': 'ALL_OF', 'scope_ref_list': ['482f24b5-0a60-4ec2-a63a-9ed00bc2534e']} + + cur_time = datetime.utcnow() + # todo: keep track of leases, to return correct list on '/leasing/v1/lessor/leases' + lease_result_list = [] + for scope_ref in j['scope_ref_list']: + lease_result_list.append({ + "ordinal": 0, + "lease": { + "ref": scope_ref, + "created": cur_time, + "expires": cur_time + timedelta(days=90), + "recommended_lease_renewal": 0.15, + "offline_lease": "true", + "license_type": "CONCURRENT_COUNTED_SINGLE" + } + }) + + response = { + "lease_result_list": lease_result_list, + "result_code": "SUCCESS", + "sync_timestamp": cur_time, + "prompts": None + } + + return response + + +# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py +@app.get('/leasing/v1/lessor/leases') +async def lease(request: Request): + cur_time = datetime.utcnow() + # venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql + response = { + # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE + "active_lease_list": [ + "BE276D7B-2CDB-11EC-9838-061A22468B59" + ], + "sync_timestamp": cur_time, + "prompts": None + } + + return response + + +@app.delete('/leasing/v1/lessor/leases') +async def lease_remove(request: Request, status_code=200): + cur_time = datetime.utcnow() + response = { + "released_lease_list": None, + "release_failure_list": None, + "sync_timestamp": cur_time, + "prompts": None + } + return response + + +if __name__ == '__main__': + import uvicorn + + ssl_keyfile = 'key.pem' + ssl_certfile = 'cert.pem' + + uvicorn.run('main:app', host='0.0.0.0', port=443, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile) From ca76ab03bb62c1a6d8ef6a887b29736d9c2a0da2 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Fri, 16 Dec 2022 13:53:32 +0100 Subject: [PATCH 04/37] main.py - added health endpoint --- app/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index 71007c5..4c2cff1 100644 --- a/app/main.py +++ b/app/main.py @@ -9,7 +9,7 @@ from datetime import datetime, timedelta from calendar import timegm from jose import jws, jwk, jwt from jose.constants import ALGORITHMS -from starlette.responses import StreamingResponse +from starlette.responses import StreamingResponse, JSONResponse from app.helper import load_key, private_bytes, public_key @@ -28,6 +28,11 @@ async def index(): return {'hello': 'world'} +@app.get('/status') +async def status(request: Request): + return JSONResponse({'status': 'up'}) + + # venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py @app.get('/client-token') async def client_token(): From cbe213ace38938ab1cfbe27d64be1b3d38cf7052 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Fri, 16 Dec 2022 13:54:55 +0100 Subject: [PATCH 05/37] main.py - added todo --- app/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/main.py b/app/main.py index 4c2cff1..ce58354 100644 --- a/app/main.py +++ b/app/main.py @@ -13,6 +13,8 @@ from starlette.responses import StreamingResponse, JSONResponse from app.helper import load_key, private_bytes, public_key +# todo: initialize certificate (or should be done by user, and passed through "volumes"?) + app = FastAPI() URL = '192.168.178.196' From 1a697a911195929aba1f2ea5e8c1c5fa3393fe86 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Fri, 16 Dec 2022 13:55:17 +0100 Subject: [PATCH 06/37] Dockerfile & .gitlab-ci.yaml --- .gitlab-ci.yml | 26 ++++++++++++++++++++++++++ Dockerfile | 16 ++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 .gitlab-ci.yml create mode 100644 Dockerfile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..b716d5a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,26 @@ +cache: + key: one-key-to-rule-them-all + +build: + image: docker:dind + interruptible: true + stage: build + rules: + - if: $CI_COMMIT_BRANCH + tags: [ docker ] + script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + - docker build . --tag ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF} + - docker push ${CI_REGISTRY}/${CI_PROJECT_PATH}/${CI_BUILD_REF_NAME}:${CI_BUILD_REF} + +test: + stage: test + script: + - echo "Nothing to do ..." + +deploy: + stage: deploy + rules: + - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + script: + - echo "Nothing to do ..." diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5430cfa --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3.10-alpine + +COPY requirements.txt /tmp/requirements.txt + +RUN apk update \ + && apk add --no-cache --virtual build-deps gcc g++ python3-dev musl-dev \ + && apk add --no-cache postgresql postgresql-dev mariadb-connector-c-dev \ + && pip install --no-cache-dir --upgrade uvicorn \ + && pip install --no-cache-dir psycopg2==2.9.3 mysqlclient==2.1.1 pysqlite3==0.4.7 \ + && pip install --no-cache-dir -r /tmp/requirements.txt \ + && apk del build-deps + +COPY app /app + +HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --fail http://localhost/status || exit 1 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "443", "--app-dir", "/app", "--proxy-headers"] From 7042862600304e1a1c990dea6c18f801b12eab45 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Fri, 16 Dec 2022 13:55:25 +0100 Subject: [PATCH 07/37] requirements.txt --- requirements.txt | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..5cc4f28 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +fastapi==0.88.0 +uvicorn[standard]==0.20.0 +python-jose==3.3.0 +cryptography==38.0.4 \ No newline at end of file From fd73ac5f2064b2a5e0a28ae36675cedf4288f9b4 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 12:58:48 +0100 Subject: [PATCH 08/37] migrated from "cryptography" to "pycryptodome" --- app/helper.py | 24 ++++++++---------------- requirements.txt | 2 +- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/app/helper.py b/app/helper.py index 6684de6..b416ab3 100644 --- a/app/helper.py +++ b/app/helper.py @@ -1,6 +1,5 @@ -from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKeyWithSerialization -from cryptography.hazmat.primitives.serialization import load_pem_private_key, Encoding, PrivateFormat, PublicFormat, \ - NoEncryption +from Crypto.PublicKey import RSA +from Crypto.PublicKey.RSA import RsaKey def load_file(filename) -> bytes: @@ -9,20 +8,13 @@ def load_file(filename) -> bytes: return content -def load_key(filename) -> RSAPrivateKeyWithSerialization: - return load_pem_private_key(data=load_file(filename), password=None) +def load_key(filename) -> RsaKey: + return RSA.import_key(extern_key=load_file(filename), passphrase=None) -def private_bytes(rsa: RSAPrivateKeyWithSerialization) -> bytes: - return rsa.private_bytes( - encoding=Encoding.PEM, - format=PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=NoEncryption() - ) +def private_bytes(rsa: RsaKey) -> bytes: + return rsa.export_key(format='PEM', passphrase=None, protection=None) -def public_key(rsa: RSAPrivateKeyWithSerialization) -> bytes: - return rsa.public_key().public_bytes( - encoding=Encoding.PEM, - format=PublicFormat.SubjectPublicKeyInfo - ) +def public_key(rsa: RsaKey) -> bytes: + return rsa.public_key().export_key(format='PEM') diff --git a/requirements.txt b/requirements.txt index 5cc4f28..878b29d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ fastapi==0.88.0 uvicorn[standard]==0.20.0 python-jose==3.3.0 -cryptography==38.0.4 \ No newline at end of file +pycryptodome==3.16.0 From 057755758597e179e85bffec2bc0560efab6c0dc Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 12:59:40 +0100 Subject: [PATCH 09/37] requirements.txt - added "python-dateutil" --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 878b29d..d0361f2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ fastapi==0.88.0 uvicorn[standard]==0.20.0 python-jose==3.3.0 pycryptodome==3.16.0 +python-dateutil==2.8.2 From 2aa0a1bc218dcd279ea84af3249f04e9fbc18422 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 12:59:58 +0100 Subject: [PATCH 10/37] README.md - updated installation section --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6232612..49f6720 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,19 @@ Minimal Licencing Servie. - -## Get token file +## Installation **The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.** + +### Linux + +```shell +curl --insecure -X GET https:///client-token -o /etc/nvidia/ClientConfigToken/client_configuration_token.tok +service nvidia-gridd restart +nvidia-smi -q | grep "License" +``` + +### Windows + +Download file and place it into `:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`. +Now restart `NvContainerLocalSystem` service. From fbf91b36359833e807dd20f5ad2110b189e065a0 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:10:43 +0100 Subject: [PATCH 11/37] typos --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 49f6720..7effddd 100644 --- a/README.md +++ b/README.md @@ -16,5 +16,5 @@ nvidia-smi -q | grep "License" ### Windows -Download file and place it into `:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`. +Download file and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`. Now restart `NvContainerLocalSystem` service. From 03089e82e5c62a4207d4b8809c303c2341e5e653 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:11:28 +0100 Subject: [PATCH 12/37] replaced "timedelta" with "relativedelta" --- app/main.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/main.py b/app/main.py index ce58354..c30b0c7 100644 --- a/app/main.py +++ b/app/main.py @@ -5,7 +5,8 @@ from uuid import uuid4 from fastapi import FastAPI, HTTPException from fastapi.requests import Request import json -from datetime import datetime, timedelta +from datetime import datetime +from dateutil.relativedelta import relativedelta from calendar import timegm from jose import jws, jwk, jwt from jose.constants import ALGORITHMS @@ -45,7 +46,7 @@ async def client_token(): }, cur_time = datetime.utcnow() - exp_time = cur_time + timedelta(days=1) + exp_time = cur_time + relativedelta(years=12) payload = { "jti": str(uuid4()), "iss": "NLS Service Instance", @@ -126,7 +127,7 @@ async def code(request: Request): # {"code_challenge":"QhDaArKDQwFeQ5Jq4Dn5hy37ODF8Jq3igXCXvWEgs5I","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"} cur_time = datetime.utcnow() - expires = cur_time + timedelta(days=1) + expires = cur_time + relativedelta(days=1) payload = { 'iat': timegm(cur_time.timetuple()), @@ -171,7 +172,7 @@ async def token(request: Request): raise HTTPException(status_code=403, detail='expected challenge did not match verifier') cur_time = datetime.utcnow() - access_expires_on = cur_time + timedelta(days=1) + access_expires_on = cur_time + relativedelta(days=1) new_payload = { 'iat': timegm(cur_time.timetuple()), @@ -216,7 +217,7 @@ async def lessor(request: Request): "lease": { "ref": scope_ref, "created": cur_time, - "expires": cur_time + timedelta(days=90), + "expires": cur_time + relativedelta(minutes=15), # days=90 "recommended_lease_renewal": 0.15, "offline_lease": "true", "license_type": "CONCURRENT_COUNTED_SINGLE" From 35e5d70bba5b8036ff1ad6b0ced48e9e3f770fce Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:14:21 +0100 Subject: [PATCH 13/37] code styling --- app/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index c30b0c7..d09d55f 100644 --- a/app/main.py +++ b/app/main.py @@ -167,8 +167,7 @@ async def token(request: Request): payload = jwt.decode(token=j['auth_code'], key=key) # validate the code challenge - if payload['challenge'] != b64encode(sha256(j['code_verifier'].encode('utf-8')).digest()).rstrip(b'=').decode( - 'utf-8'): + if payload['challenge'] != b64encode(sha256(j['code_verifier'].encode('utf-8')).digest()).rstrip(b'=').decode('utf-8'): raise HTTPException(status_code=403, detail='expected challenge did not match verifier') cur_time = datetime.utcnow() From b7da2bb6481ef4a8a88ae0b3c41c2b3cd18ca871 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:14:31 +0100 Subject: [PATCH 14/37] typos --- app/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index d09d55f..6da9c66 100644 --- a/app/main.py +++ b/app/main.py @@ -12,7 +12,7 @@ from jose import jws, jwk, jwt from jose.constants import ALGORITHMS from starlette.responses import StreamingResponse, JSONResponse -from app.helper import load_key, private_bytes, public_key +from helper import load_key, private_bytes, public_key # todo: initialize certificate (or should be done by user, and passed through "volumes"?) From 0db526a2d684e1c8bec177eae2bfe0363bffcd7a Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:14:53 +0100 Subject: [PATCH 15/37] added timestamp to download filename --- app/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index 6da9c66..ea9de3c 100644 --- a/app/main.py +++ b/app/main.py @@ -85,7 +85,7 @@ async def client_token(): data = jwt.encode(payload, key=key, headers=None, algorithm='RS256') response = StreamingResponse(iter([data]), media_type="text/plain") - response.headers["Content-Disposition"] = "attachment; filename=client_token.tok" + response.headers["Content-Disposition"] = f'attachment; filename=client_configuration_token_{datetime.now().strftime("%d-%m-%y-%H-%M-%S")}' return response From 16e4fc4158e46a3578854c6f71bcf49e3470fe28 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:15:19 +0100 Subject: [PATCH 16/37] added lease update request (PUT) --- app/main.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/app/main.py b/app/main.py index ea9de3c..2af2722 100644 --- a/app/main.py +++ b/app/main.py @@ -18,6 +18,8 @@ from helper import load_key, private_bytes, public_key app = FastAPI() +LEASE_EXPIRE_DELTA = relativedelta(minutes=15) # days=90 + URL = '192.168.178.196' SITE_KEY_FILE = load_key('/opt/fastapi-dls/site.key') SITE_KEY_XID = '00000000-0000-0000-0000-000000000000' @@ -216,7 +218,7 @@ async def lessor(request: Request): "lease": { "ref": scope_ref, "created": cur_time, - "expires": cur_time + relativedelta(minutes=15), # days=90 + "expires": cur_time + LEASE_EXPIRE_DELTA, "recommended_lease_renewal": 0.15, "offline_lease": "true", "license_type": "CONCURRENT_COUNTED_SINGLE" @@ -250,6 +252,25 @@ async def lease(request: Request): return response +# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py +@app.put('/leasing/v1/lease/{lease_ref}') +async def lease_renew(request: Request, lease_ref: str): + print('> renew') + + cur_time = datetime.utcnow() + + response = { + "lease_ref": lease_ref, + "expires": cur_time + LEASE_EXPIRE_DELTA, + "recommended_lease_renewal": 0.16, + "offline_lease": True, + "prompts": None, + "sync_timestamp": cur_time + } + + return response + + @app.delete('/leasing/v1/lessor/leases') async def lease_remove(request: Request, status_code=200): cur_time = datetime.utcnow() From 863821121ce0e8c0aab2db72189eb60a5062baf8 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:20:33 +0100 Subject: [PATCH 17/37] .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 1cdf68f..6e1f44b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .DS_Store venv/ .idea/ +app/cert/*.* From c6607bedba6e1be824dc34f4fad7d101d45d7712 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:35:03 +0100 Subject: [PATCH 18/37] main.py - added some documentation for dev-server --- app/main.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index 2af2722..c6c6600 100644 --- a/app/main.py +++ b/app/main.py @@ -286,7 +286,18 @@ async def lease_remove(request: Request, status_code=200): if __name__ == '__main__': import uvicorn - ssl_keyfile = 'key.pem' - ssl_certfile = 'cert.pem' + ### + # + # Running `python app/main.py` assumes that the user created a keypair, e.g. with openssl. + # + # openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout app/cert/webserver.key -out app/cert/webserver.crt + # + ### - uvicorn.run('main:app', host='0.0.0.0', port=443, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile) + + print(f'> Starting dev-server ...') + + ssl_keyfile = 'cert/webserver.key' + ssl_certfile = 'cert/webserver.crt' + + uvicorn.run('main:app', host='0.0.0.0', port=443, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, reload=True) From 78ddaa56d374cb2e96ff2243095768d549ccdd9f Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 13:52:16 +0100 Subject: [PATCH 19/37] main.py - replaced SITE_KEY and INSTANCE_KEY with only INSTANCE_KEY --- app/main.py | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/app/main.py b/app/main.py index c6c6600..3a16bff 100644 --- a/app/main.py +++ b/app/main.py @@ -21,11 +21,9 @@ app = FastAPI() LEASE_EXPIRE_DELTA = relativedelta(minutes=15) # days=90 URL = '192.168.178.196' -SITE_KEY_FILE = load_key('/opt/fastapi-dls/site.key') SITE_KEY_XID = '00000000-0000-0000-0000-000000000000' - -SITE_KEY_RSA = private_bytes(SITE_KEY_FILE) -SITE_KEY_PUB = public_key(SITE_KEY_FILE) +INSTANCE_KEY_RSA = load_key('cert/instance.private.pem') +INSTANCE_KEY_PUB = load_key('cert/instance.public.pem') @app.get('/') @@ -41,11 +39,10 @@ async def status(request: Request): # venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py @app.get('/client-token') async def client_token(): - public_key_me = SITE_KEY_FILE.public_key().public_numbers() service_instance_public_key_me = { - "mod": hex(public_key_me.n)[2:], - "exp": public_key_me.e, - }, + "mod": hex(INSTANCE_KEY_PUB.public_key().n)[2:], + "exp": INSTANCE_KEY_PUB.public_key().e, + } cur_time = datetime.utcnow() exp_time = cur_time + relativedelta(years=12) @@ -53,9 +50,9 @@ async def client_token(): "jti": str(uuid4()), "iss": "NLS Service Instance", "aud": "NLS Licensed Client", - "iat": cur_time, - "nbf": cur_time, - "exp": exp_time, + "iat": timegm(cur_time.timetuple()), + "nbf": timegm(cur_time.timetuple()), + "exp": timegm(exp_time.timetuple()), "update_mode": "ABSOLUTE", "scope_ref_list": [ "482f24b5-0a60-4ec2-a63a-9ed00bc2534e" @@ -78,13 +75,13 @@ async def client_token(): }, "service_instance_public_key_configuration": { "service_instance_public_key_me": service_instance_public_key_me, - "service_instance_public_key_pem": SITE_KEY_PUB.decode('utf-8'), + "service_instance_public_key_pem": INSTANCE_KEY_PUB.export_key().decode('utf-8'), "key_retention_mode": "LATEST_ONLY" } } - key = jwk.construct(SITE_KEY_RSA, algorithm=ALGORITHMS.RS512) - data = jwt.encode(payload, key=key, headers=None, algorithm='RS256') + key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256) + data = jws.sign(payload, key=key, headers=None, algorithm='RS256') response = StreamingResponse(iter([data]), media_type="text/plain") response.headers["Content-Disposition"] = f'attachment; filename=client_configuration_token_{datetime.now().strftime("%d-%m-%y-%H-%M-%S")}' @@ -144,7 +141,7 @@ async def code(request: Request): kid = payload.get('kid') if kid: headers = {'kid': kid} - key = jwk.construct(SITE_KEY_RSA, algorithm=ALGORITHMS.RS512) + key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512) auth_code = jws.sign(payload, key, headers=headers, algorithm='RS256') response = { @@ -165,7 +162,7 @@ async def token(request: Request): # {"auth_code":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzExODI5MTQsImV4cCI6MTY3MTI2OTMxNCwiY2hhbGxlbmdlIjoiaXdZdFpIME03K0ZZUWdRQXEwbjhabThWcFpJbWdtV1NDSXI1MkdTSlMxayIsIm9yaWdpbl9yZWYiOiJpd1l0WkgwTTcrRllRZ1FBcTBuOFptOFZwWkltZ21XU0NJcjUyR1NKUzFrIiwia2V5X3JlZiI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsImtpZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCJ9.hkBPQx7UbXqwRzpTSp5fASwLg7rJOgjDOGD98Zh6pEkPW09KjxcsaHKeR8KIZmDS1S_kLed93-UzUY4wXAylFBlM-daL-TEbHJau2muZGWXPrtdsGLI9CLFcc0dmocq1_5rnRV3liqjdZwL8djK9Fx_5tOzEfeI9oCJ49Sh2LD_p1vkFcqUv9z9mVL9IGsoRM6y4hJ2YKBloijzhMLp5E7nojyD6Z8PQZ0mOIOc3tncAaXQS47JhgGsJPUDR-YoLF5uNpAlJKZP2eZWJt3P7MvhIz3lxFPUJ5jHX64Vf0Ds10-GBctZuy1-eCLBXj74uQy_U4KlnCif-5N8bPTvgxw","code_verifier":"CgnDPaugQCb4U6l3EfJSFsA/JxMqNO4TqONeb9yl8EVRWU88yTPlEeJgZQO0f/JVnScYOsvwa0jcvTAMBulEKgucfxDDVL1cBOylGugQ0QlJsXU5hJ8VLAQtOyPthnVyEutERNyOKVwl3YI5Z5EfUcfuhDqmxBUpnAFtQ9H3R3g"} # payload = self._security.get_valid_payload(req.auth_code) # todo - key = jwk.construct(SITE_KEY_PUB, algorithm=ALGORITHMS.RS512) + key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512) payload = jwt.decode(token=j['auth_code'], key=key) # validate the code challenge @@ -190,7 +187,7 @@ async def token(request: Request): kid = payload.get('kid') if kid: headers = {'kid': kid} - key = jwk.construct(SITE_KEY_RSA, algorithm=ALGORITHMS.RS512) + key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512) auth_token = jwt.encode(new_payload, key=key, headers=headers, algorithm='RS256') response = { From 15acb6fdbe799727044366a8273d254e43436d46 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 14:11:38 +0100 Subject: [PATCH 20/37] code styling --- app/main.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/main.py b/app/main.py index 3a16bff..88ed830 100644 --- a/app/main.py +++ b/app/main.py @@ -252,8 +252,6 @@ async def lease(request: Request): # venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py @app.put('/leasing/v1/lease/{lease_ref}') async def lease_renew(request: Request, lease_ref: str): - print('> renew') - cur_time = datetime.utcnow() response = { @@ -291,7 +289,6 @@ if __name__ == '__main__': # ### - print(f'> Starting dev-server ...') ssl_keyfile = 'cert/webserver.key' From 1f8f85479fb979dd762c7460f2ab26c4a50d3681 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 14:27:10 +0100 Subject: [PATCH 21/37] main.py - fixed file imports --- app/main.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/main.py b/app/main.py index 88ed830..afdb6a4 100644 --- a/app/main.py +++ b/app/main.py @@ -1,7 +1,7 @@ from base64 import b64encode from hashlib import sha256 from uuid import uuid4 - +from os.path import join, dirname from fastapi import FastAPI, HTTPException from fastapi.requests import Request import json @@ -22,8 +22,8 @@ LEASE_EXPIRE_DELTA = relativedelta(minutes=15) # days=90 URL = '192.168.178.196' SITE_KEY_XID = '00000000-0000-0000-0000-000000000000' -INSTANCE_KEY_RSA = load_key('cert/instance.private.pem') -INSTANCE_KEY_PUB = load_key('cert/instance.public.pem') +INSTANCE_KEY_RSA = load_key(join(dirname(__file__), 'cert/instance.private.pem')) +INSTANCE_KEY_PUB = load_key(join(dirname(__file__), 'cert/instance.public.pem')) @app.get('/') @@ -291,7 +291,7 @@ if __name__ == '__main__': print(f'> Starting dev-server ...') - ssl_keyfile = 'cert/webserver.key' - ssl_certfile = 'cert/webserver.crt' + ssl_keyfile = join(dirname(__file__), 'cert/webserver.key') + ssl_certfile = join(dirname(__file__), 'cert/webserver.crt') uvicorn.run('main:app', host='0.0.0.0', port=443, ssl_keyfile=ssl_keyfile, ssl_certfile=ssl_certfile, reload=True) From 8e95f700efff56f4f98cdadf63ea489952314480 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 14:33:23 +0100 Subject: [PATCH 22/37] Dockerfile - temporary added uvicorn ssl support (in future releases uvicorn listens to http and tls termination should be done by a proxy like traefik) --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 5430cfa..78bae1b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,4 +13,4 @@ RUN apk update \ COPY app /app HEALTHCHECK --start-period=30s --interval=10s --timeout=5s --retries=3 CMD curl --fail http://localhost/status || exit 1 -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "443", "--app-dir", "/app", "--proxy-headers"] +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "443", "--app-dir", "/app", "--proxy-headers", "--ssl-keyfile", "/app/cert/webserver.key", "--ssl-certfile", "/app/cert/webserver.crt"] From 30256b7272d66a3a267f59609aab19b38258e8c9 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 14:44:26 +0100 Subject: [PATCH 23/37] added some env variables --- app/main.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/main.py b/app/main.py index afdb6a4..12a0e5e 100644 --- a/app/main.py +++ b/app/main.py @@ -2,6 +2,7 @@ from base64 import b64encode from hashlib import sha256 from uuid import uuid4 from os.path import join, dirname +from os import getenv from fastapi import FastAPI, HTTPException from fastapi.requests import Request import json @@ -20,8 +21,9 @@ app = FastAPI() LEASE_EXPIRE_DELTA = relativedelta(minutes=15) # days=90 -URL = '192.168.178.196' -SITE_KEY_XID = '00000000-0000-0000-0000-000000000000' +DLS_URL = getenv('DLS_URL', 'localhost') +DLS_PORT = getenv('DLS_PORT', 443) +SITE_KEY_XID = getenv('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000') INSTANCE_KEY_RSA = load_key(join(dirname(__file__), 'cert/instance.private.pem')) INSTANCE_KEY_PUB = load_key(join(dirname(__file__), 'cert/instance.public.pem')) @@ -66,12 +68,12 @@ async def client_token(): "idx": 0, "d_name": "DLS", "svc_port_map": [ - {"service": "auth", "port": 443}, - {"service": "lease", "port": 443} + {"service": "auth", "port": DLS_PORT}, + {"service": "lease", "port": DLS_PORT} ] } ], - "node_url_list": [{"idx": 0, "url": URL, "url_qr": URL, "svc_port_set_idx": 0}] + "node_url_list": [{"idx": 0, "url": DLS_URL, "url_qr": DLS_URL, "svc_port_set_idx": 0}] }, "service_instance_public_key_configuration": { "service_instance_public_key_me": service_instance_public_key_me, From cdb7fe777e2db2cb3918a9bb5ab47b2734c5584a Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 14:48:13 +0100 Subject: [PATCH 24/37] fixed variable types --- app/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index 12a0e5e..4b8cce1 100644 --- a/app/main.py +++ b/app/main.py @@ -21,8 +21,8 @@ app = FastAPI() LEASE_EXPIRE_DELTA = relativedelta(minutes=15) # days=90 -DLS_URL = getenv('DLS_URL', 'localhost') -DLS_PORT = getenv('DLS_PORT', 443) +DLS_URL = str(getenv('DLS_URL', 'localhost')) +DLS_PORT = int(getenv('DLS_PORT', '443')) SITE_KEY_XID = getenv('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000') INSTANCE_KEY_RSA = load_key(join(dirname(__file__), 'cert/instance.private.pem')) INSTANCE_KEY_PUB = load_key(join(dirname(__file__), 'cert/instance.public.pem')) From dff38154d125afe67d1188f40fc81229c23dc7bc Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 15:51:49 +0100 Subject: [PATCH 25/37] main.py - fixes --- app/main.py | 37 ++++++++++++++----------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/app/main.py b/app/main.py index 4b8cce1..b2569a1 100644 --- a/app/main.py +++ b/app/main.py @@ -41,13 +41,18 @@ async def status(request: Request): # venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py @app.get('/client-token') async def client_token(): - service_instance_public_key_me = { - "mod": hex(INSTANCE_KEY_PUB.public_key().n)[2:], - "exp": INSTANCE_KEY_PUB.public_key().e, - } - cur_time = datetime.utcnow() exp_time = cur_time + relativedelta(years=12) + + service_instance_public_key_configuration = { + "service_instance_public_key_me": { + "mod": hex(INSTANCE_KEY_PUB.public_key().n)[2:], + "exp": INSTANCE_KEY_PUB.public_key().e, + }, + "service_instance_public_key_pem": INSTANCE_KEY_PUB.export_key().decode('utf-8'), + "key_retention_mode": "LATEST_ONLY" + } + payload = { "jti": str(uuid4()), "iss": "NLS Service Instance", @@ -56,13 +61,10 @@ async def client_token(): "nbf": timegm(cur_time.timetuple()), "exp": timegm(exp_time.timetuple()), "update_mode": "ABSOLUTE", - "scope_ref_list": [ - "482f24b5-0a60-4ec2-a63a-9ed00bc2534e" - # todo: "scope_ref_list" should be a unique client id (which identifies leases, etc.) - ], + "scope_ref_list": [str(uuid4())], "fulfillment_class_ref_list": [], "service_instance_configuration": { - "nls_service_instance_ref": "b43d6e46-d6d0-4943-8b8d-c66a5f6e0d38", + "nls_service_instance_ref": "00000000-0000-0000-0000-000000000000", "svc_port_set_list": [ { "idx": 0, @@ -75,11 +77,7 @@ async def client_token(): ], "node_url_list": [{"idx": 0, "url": DLS_URL, "url_qr": DLS_URL, "svc_port_set_idx": 0}] }, - "service_instance_public_key_configuration": { - "service_instance_public_key_me": service_instance_public_key_me, - "service_instance_public_key_pem": INSTANCE_KEY_PUB.export_key().decode('utf-8'), - "key_retention_mode": "LATEST_ONLY" - } + "service_instance_public_key_configuration": service_instance_public_key_configuration, } key = jwk.construct(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS256) @@ -101,14 +99,7 @@ async def auth(request: Request, status_code=201): cur_time = datetime.utcnow() response = { "origin_ref": j['candidate_origin_ref'], - "environment": { - "fingerprint": {"mac_address_list": ["e4:b9:7a:e5:7b:ff"]}, - "guest_driver_version": "guest_driver_version", - "hostname": "myhost", - "os_platform": "os_platform", - "os_version": "os_version", - "ip_address_list": ["192.168.1.129"] - }, + "environment": j['environment'], "svc_port_set_list": None, "node_url_list": None, "node_query_order": None, From fd73f9994235fba88d2101c9c9d5a1588c7fae62 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 15:53:15 +0100 Subject: [PATCH 26/37] code styling --- app/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index b2569a1..bee6bc6 100644 --- a/app/main.py +++ b/app/main.py @@ -94,7 +94,7 @@ async def auth(request: Request, status_code=201): body = await request.body() body = body.decode('utf-8') j = json.loads(body) - # {"candidate_origin_ref":"00112233-4455-6677-8899-aabbccddeeff","environment":{"fingerprint":{"mac_address_list":["fa:52:16:65:c5:28"]},"hostname":"debian-grid-test","ip_address_list":["192.168.178.12","fdfe:7fcd:e30f:40f5:f852:16ff:fe65:c528","fe80::f852:16ff:fe65:c528%enp6s18"],"guest_driver_version":"510.85.02","os_platform":"Debian GNU/Linux 11 (bullseye) 11","os_version":"11 (bullseye)"},"registration_pending":false,"update_pending":false} + # {"candidate_origin_ref":"00112233-4455-6677-8899-aabbccddeeff","environment":{"fingerprint":{"mac_address_list":["ff:ff:ff:ff:ff:ff"]},"hostname":"my-hostname","ip_address_list":["192.168.178.123","fe80::","fe80::1%enp6s18"],"guest_driver_version":"510.85.02","os_platform":"Debian GNU/Linux 11 (bullseye) 11","os_version":"11 (bullseye)"},"registration_pending":false,"update_pending":false} cur_time = datetime.utcnow() response = { @@ -152,7 +152,7 @@ async def token(request: Request): body = await request.body() body = body.decode('utf-8') j = json.loads(body) - # {"auth_code":"eyJhbGciOiJSUzI1NiIsImtpZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NzExODI5MTQsImV4cCI6MTY3MTI2OTMxNCwiY2hhbGxlbmdlIjoiaXdZdFpIME03K0ZZUWdRQXEwbjhabThWcFpJbWdtV1NDSXI1MkdTSlMxayIsIm9yaWdpbl9yZWYiOiJpd1l0WkgwTTcrRllRZ1FBcTBuOFptOFZwWkltZ21XU0NJcjUyR1NKUzFrIiwia2V5X3JlZiI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCIsImtpZCI6IjAwMDAwMDAwLTAwMDAtMDAwMC0wMDAwLTAwMDAwMDAwMDAwMCJ9.hkBPQx7UbXqwRzpTSp5fASwLg7rJOgjDOGD98Zh6pEkPW09KjxcsaHKeR8KIZmDS1S_kLed93-UzUY4wXAylFBlM-daL-TEbHJau2muZGWXPrtdsGLI9CLFcc0dmocq1_5rnRV3liqjdZwL8djK9Fx_5tOzEfeI9oCJ49Sh2LD_p1vkFcqUv9z9mVL9IGsoRM6y4hJ2YKBloijzhMLp5E7nojyD6Z8PQZ0mOIOc3tncAaXQS47JhgGsJPUDR-YoLF5uNpAlJKZP2eZWJt3P7MvhIz3lxFPUJ5jHX64Vf0Ds10-GBctZuy1-eCLBXj74uQy_U4KlnCif-5N8bPTvgxw","code_verifier":"CgnDPaugQCb4U6l3EfJSFsA/JxMqNO4TqONeb9yl8EVRWU88yTPlEeJgZQO0f/JVnScYOsvwa0jcvTAMBulEKgucfxDDVL1cBOylGugQ0QlJsXU5hJ8VLAQtOyPthnVyEutERNyOKVwl3YI5Z5EfUcfuhDqmxBUpnAFtQ9H3R3g"} + # {"auth_code":"...","code_verifier":"..."} # payload = self._security.get_valid_payload(req.auth_code) # todo key = jwk.construct(INSTANCE_KEY_PUB.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512) @@ -197,7 +197,7 @@ async def lessor(request: Request): body = await request.body() body = body.decode('utf-8') j = json.loads(body) - # {'fulfillment_context': {'fulfillment_class_ref_list': []}, 'lease_proposal_list': [{'license_type_qualifiers': {'count': 1}, 'product': {'name': 'NVIDIA RTX Virtual Workstation'}}], 'proposal_evaluation_mode': 'ALL_OF', 'scope_ref_list': ['482f24b5-0a60-4ec2-a63a-9ed00bc2534e']} + # {'fulfillment_context': {'fulfillment_class_ref_list': []}, 'lease_proposal_list': [{'license_type_qualifiers': {'count': 1}, 'product': {'name': 'NVIDIA RTX Virtual Workstation'}}], 'proposal_evaluation_mode': 'ALL_OF', 'scope_ref_list': ['00112233-4455-6677-8899-aabbccddeeff']} cur_time = datetime.utcnow() # todo: keep track of leases, to return correct list on '/leasing/v1/lessor/leases' From 30679d923ede047ab89e89964fd5a438596d74ab Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 16:16:44 +0100 Subject: [PATCH 27/37] code styling & added some logging --- app/main.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index bee6bc6..2f28d64 100644 --- a/app/main.py +++ b/app/main.py @@ -95,6 +95,7 @@ async def auth(request: Request, status_code=201): body = body.decode('utf-8') j = json.loads(body) # {"candidate_origin_ref":"00112233-4455-6677-8899-aabbccddeeff","environment":{"fingerprint":{"mac_address_list":["ff:ff:ff:ff:ff:ff"]},"hostname":"my-hostname","ip_address_list":["192.168.178.123","fe80::","fe80::1%enp6s18"],"guest_driver_version":"510.85.02","os_platform":"Debian GNU/Linux 11 (bullseye) 11","os_version":"11 (bullseye)"},"registration_pending":false,"update_pending":false} + print(f'> [ origin ]: {j}') cur_time = datetime.utcnow() response = { @@ -116,7 +117,8 @@ async def code(request: Request): body = await request.body() body = body.decode('utf-8') j = json.loads(body) - # {"code_challenge":"QhDaArKDQwFeQ5Jq4Dn5hy37ODF8Jq3igXCXvWEgs5I","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"} + # {"code_challenge":"...","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"} + print(f'> [ code ]: {j}') cur_time = datetime.utcnow() expires = cur_time + relativedelta(days=1) @@ -198,6 +200,7 @@ async def lessor(request: Request): body = body.decode('utf-8') j = json.loads(body) # {'fulfillment_context': {'fulfillment_class_ref_list': []}, 'lease_proposal_list': [{'license_type_qualifiers': {'count': 1}, 'product': {'name': 'NVIDIA RTX Virtual Workstation'}}], 'proposal_evaluation_mode': 'ALL_OF', 'scope_ref_list': ['00112233-4455-6677-8899-aabbccddeeff']} + print(f'> [ lessor ]: {j}') cur_time = datetime.utcnow() # todo: keep track of leases, to return correct list on '/leasing/v1/lessor/leases' @@ -245,8 +248,9 @@ async def lease(request: Request): # venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py @app.put('/leasing/v1/lease/{lease_ref}') async def lease_renew(request: Request, lease_ref: str): - cur_time = datetime.utcnow() + print(f'> [ renew ]: {lease_ref}') + cur_time = datetime.utcnow() response = { "lease_ref": lease_ref, "expires": cur_time + LEASE_EXPIRE_DELTA, From 0df63016ab69fe4ea94d25b1c048e94fac88cff2 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 16:31:33 +0100 Subject: [PATCH 28/37] main.py - added some static leases list --- app/main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index 2f28d64..56762b9 100644 --- a/app/main.py +++ b/app/main.py @@ -234,9 +234,11 @@ async def lease(request: Request): cur_time = datetime.utcnow() # venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql response = { - # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE "active_lease_list": [ - "BE276D7B-2CDB-11EC-9838-061A22468B59" + "BE276D7B-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE + "BE276EFE-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE + "BE277164-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE + "BE277214-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE ], "sync_timestamp": cur_time, "prompts": None From 7676c6942746ca5e84ac9343f42693c925cd4d0d Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 16:46:12 +0100 Subject: [PATCH 29/37] main.py - try to fix "active_lease_list" --- app/main.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/main.py b/app/main.py index 56762b9..8b970d7 100644 --- a/app/main.py +++ b/app/main.py @@ -235,10 +235,8 @@ async def lease(request: Request): # venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql response = { "active_lease_list": [ - "BE276D7B-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE - "BE276EFE-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE - "BE277164-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE - "BE277214-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE + "BE276D7B-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE 2 + "BE276EFE-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE 1 ], "sync_timestamp": cur_time, "prompts": None From d7d81a48c7ed97fefac56395261212e60eaa8863 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 17:44:42 +0100 Subject: [PATCH 30/37] main.py - try to fix "active_lease_list" --- app/main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index 8b970d7..3bc376e 100644 --- a/app/main.py +++ b/app/main.py @@ -235,8 +235,9 @@ async def lease(request: Request): # venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql response = { "active_lease_list": [ - "BE276D7B-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE 2 - "BE276EFE-2CDB-11EC-9838-061A22468B59", # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE 1 + "BE276D7B-2CDB-11EC-9838-061A22468B59", # 'BE276D7B-2CDB-11EC-9838-061A22468B59','BDFBE16D-2CDB-11EC-9838-061A22468B59' => 'NVIDIA Virtual PC','NVIDIA Virtual PC' + "BE276EFE-2CDB-11EC-9838-061A22468B59", # 'BE276EFE-2CDB-11EC-9838-061A22468B59','BDFBE308-2CDB-11EC-9838-061A22468B59' => 'NVIDIA RTX Virtual Workstation','NVIDIA RTX Virtual Workstation' + "BE276FF0-2CDB-11EC-9838-061A22468B59", # 'BE276FF0-2CDB-11EC-9838-061A22468B59','BDFBE405-2CDB-11EC-9838-061A22468B59' => 'NVIDIA vGaming','NVIDIA vGaming' ], "sync_timestamp": cur_time, "prompts": None From 5ee417825dcd60c031e4ec54b2a606f88aee91eb Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 22:16:42 +0100 Subject: [PATCH 31/37] main.py - leases - rollback to @0df63016 --- app/main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index 3bc376e..2f28d64 100644 --- a/app/main.py +++ b/app/main.py @@ -234,10 +234,9 @@ async def lease(request: Request): cur_time = datetime.utcnow() # venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql response = { + # GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE "active_lease_list": [ - "BE276D7B-2CDB-11EC-9838-061A22468B59", # 'BE276D7B-2CDB-11EC-9838-061A22468B59','BDFBE16D-2CDB-11EC-9838-061A22468B59' => 'NVIDIA Virtual PC','NVIDIA Virtual PC' - "BE276EFE-2CDB-11EC-9838-061A22468B59", # 'BE276EFE-2CDB-11EC-9838-061A22468B59','BDFBE308-2CDB-11EC-9838-061A22468B59' => 'NVIDIA RTX Virtual Workstation','NVIDIA RTX Virtual Workstation' - "BE276FF0-2CDB-11EC-9838-061A22468B59", # 'BE276FF0-2CDB-11EC-9838-061A22468B59','BDFBE405-2CDB-11EC-9838-061A22468B59' => 'NVIDIA vGaming','NVIDIA vGaming' + "BE276D7B-2CDB-11EC-9838-061A22468B59" ], "sync_timestamp": cur_time, "prompts": None From 1a8404b4bb44e8fb10f4a6dc503c071a22829c6d Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Mon, 19 Dec 2022 22:20:50 +0100 Subject: [PATCH 32/37] code styling --- app/main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/main.py b/app/main.py index 2f28d64..8217293 100644 --- a/app/main.py +++ b/app/main.py @@ -84,13 +84,14 @@ async def client_token(): data = jws.sign(payload, key=key, headers=None, algorithm='RS256') response = StreamingResponse(iter([data]), media_type="text/plain") - response.headers["Content-Disposition"] = f'attachment; filename=client_configuration_token_{datetime.now().strftime("%d-%m-%y-%H-%M-%S")}' + filename = f'client_configuration_token_{datetime.now().strftime("%d-%m-%y-%H-%M-%S")}' + response.headers["Content-Disposition"] = f'attachment; filename={filename}' return response # venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py @app.post('/auth/v1/origin') -async def auth(request: Request, status_code=201): +async def auth(request: Request): body = await request.body() body = body.decode('utf-8') j = json.loads(body) @@ -248,7 +249,7 @@ async def lease(request: Request): # venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py @app.put('/leasing/v1/lease/{lease_ref}') async def lease_renew(request: Request, lease_ref: str): - print(f'> [ renew ]: {lease_ref}') + print(f'> [ renew ]: lease: {lease_ref}') cur_time = datetime.utcnow() response = { From 1bdb4cf0a542fba6ef93be3347c1edff195ef2e1 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Tue, 20 Dec 2022 05:39:17 +0100 Subject: [PATCH 33/37] Reverse Engineering Notes.md - fixes --- doc/Reverse Engineering Notes.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/doc/Reverse Engineering Notes.md b/doc/Reverse Engineering Notes.md index c91b94b..6746cf7 100644 --- a/doc/Reverse Engineering Notes.md +++ b/doc/Reverse Engineering Notes.md @@ -54,6 +54,10 @@ Command: - `/etc/dls/config/decryptor/decryptor` +```shell + docker exec -it /etc/dls/config/decryptor/decryptor > /tmp/private-key.pem +``` + ``` -----BEGIN RSA PRIVATE KEY----- ... @@ -75,8 +79,9 @@ base64-content... **Decrypt database password** ``` -cat /etc/dls/config/dls_db_password.bin | base64 -d > /etc/dls/config/dls_db_password.bin.raw -openssl rsautl -decrypt -inkey /tmp/private-key.pem -in /etc/dls/config/dls_db_password.bin.raw +cd /var/lib/docker/volumes/configurations/_data +cat dls_db_password.bin | base64 -d > dls_db_password.bin.raw +openssl rsautl -decrypt -inkey /tmp/private-key.pem -in dls_db_password.bin.raw ``` # Database From e7cbc0fc59cc342b8daa65639b51e48c0f637a54 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Tue, 20 Dec 2022 06:47:40 +0100 Subject: [PATCH 34/37] main.py - improved responses with wrapping in 'JSONResponse' --- app/main.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/app/main.py b/app/main.py index 8217293..075170d 100644 --- a/app/main.py +++ b/app/main.py @@ -30,7 +30,7 @@ INSTANCE_KEY_PUB = load_key(join(dirname(__file__), 'cert/instance.public.pem')) @app.get('/') async def index(): - return {'hello': 'world'} + return JSONResponse({'hello': 'world'}) @app.get('/status') @@ -106,9 +106,9 @@ async def auth(request: Request): "node_url_list": None, "node_query_order": None, "prompts": None, - "sync_timestamp": cur_time + "sync_timestamp": cur_time.isoformat() } - return response + return JSONResponse(response) # venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py @@ -142,10 +142,10 @@ async def code(request: Request): response = { "auth_code": auth_code, - "sync_timestamp": datetime.utcnow(), + "sync_timestamp": cur_time.isoformat(), "prompts": None } - return response + return JSONResponse(response) # venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py @@ -187,12 +187,12 @@ async def token(request: Request): auth_token = jwt.encode(new_payload, key=key, headers=headers, algorithm='RS256') response = { - "expires": access_expires_on, + "expires": access_expires_on.isoformat(), "auth_token": auth_token, - "sync_timestamp": cur_time, + "sync_timestamp": cur_time.isoformat(), } - return response + return JSONResponse(response) @app.post('/leasing/v1/lessor') @@ -211,8 +211,8 @@ async def lessor(request: Request): "ordinal": 0, "lease": { "ref": scope_ref, - "created": cur_time, - "expires": cur_time + LEASE_EXPIRE_DELTA, + "created": cur_time.isoformat(), + "expires": (cur_time + LEASE_EXPIRE_DELTA).isoformat(), "recommended_lease_renewal": 0.15, "offline_lease": "true", "license_type": "CONCURRENT_COUNTED_SINGLE" @@ -222,11 +222,11 @@ async def lessor(request: Request): response = { "lease_result_list": lease_result_list, "result_code": "SUCCESS", - "sync_timestamp": cur_time, + "sync_timestamp": cur_time.isoformat(), "prompts": None } - return response + return JSONResponse(response) # venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py @@ -239,11 +239,11 @@ async def lease(request: Request): "active_lease_list": [ "BE276D7B-2CDB-11EC-9838-061A22468B59" ], - "sync_timestamp": cur_time, + "sync_timestamp": cur_time.isoformat(), "prompts": None } - return response + return JSONResponse(response) # venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py @@ -254,14 +254,14 @@ async def lease_renew(request: Request, lease_ref: str): cur_time = datetime.utcnow() response = { "lease_ref": lease_ref, - "expires": cur_time + LEASE_EXPIRE_DELTA, + "expires": (cur_time + LEASE_EXPIRE_DELTA).isoformat(), "recommended_lease_renewal": 0.16, "offline_lease": True, "prompts": None, - "sync_timestamp": cur_time + "sync_timestamp": cur_time.isoformat(), } - return response + return JSONResponse(response) @app.delete('/leasing/v1/lessor/leases') @@ -270,10 +270,10 @@ async def lease_remove(request: Request, status_code=200): response = { "released_lease_list": None, "release_failure_list": None, - "sync_timestamp": cur_time, + "sync_timestamp": cur_time.isoformat(), "prompts": None } - return response + return JSONResponse(response) if __name__ == '__main__': From 1efdf19fa6d6badd37b9e5114d2cdb909692a244 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Tue, 20 Dec 2022 07:15:34 +0100 Subject: [PATCH 35/37] code styling --- app/main.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/app/main.py b/app/main.py index 075170d..42ac2d2 100644 --- a/app/main.py +++ b/app/main.py @@ -91,7 +91,7 @@ async def client_token(): # venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py @app.post('/auth/v1/origin') -async def auth(request: Request): +async def auth_origin(request: Request): body = await request.body() body = body.decode('utf-8') j = json.loads(body) @@ -114,7 +114,7 @@ async def auth(request: Request): # venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py # venv/lib/python3.9/site-packages/nls_core_auth/auth.py - CodeResponse @app.post('/auth/v1/code') -async def code(request: Request): +async def auth_code(request: Request): body = await request.body() body = body.decode('utf-8') j = json.loads(body) @@ -151,7 +151,7 @@ async def code(request: Request): # venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py # venv/lib/python3.9/site-packages/nls_core_auth/auth.py - TokenResponse @app.post('/auth/v1/token') -async def token(request: Request): +async def auth_token(request: Request): body = await request.body() body = body.decode('utf-8') j = json.loads(body) @@ -196,7 +196,7 @@ async def token(request: Request): @app.post('/leasing/v1/lessor') -async def lessor(request: Request): +async def leasing_lessor(request: Request): body = await request.body() body = body.decode('utf-8') j = json.loads(body) @@ -231,7 +231,7 @@ async def lessor(request: Request): # venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py @app.get('/leasing/v1/lessor/leases') -async def lease(request: Request): +async def leasing_lessor_lease(request: Request): cur_time = datetime.utcnow() # venv/lib/python3.9/site-packages/nls_dal_service_instance_dls/schema/service_instance/V1_0_21__product_mapping.sql response = { @@ -248,7 +248,7 @@ async def lease(request: Request): # venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py @app.put('/leasing/v1/lease/{lease_ref}') -async def lease_renew(request: Request, lease_ref: str): +async def leasing_lease_renew(request: Request, lease_ref: str): print(f'> [ renew ]: lease: {lease_ref}') cur_time = datetime.utcnow() @@ -265,7 +265,7 @@ async def lease_renew(request: Request, lease_ref: str): @app.delete('/leasing/v1/lessor/leases') -async def lease_remove(request: Request, status_code=200): +async def leasing_lessor_lease_remove(request: Request): cur_time = datetime.utcnow() response = { "released_lease_list": None, From 88e14d2a21a4847ea2a56613bedc9a3f0eda38b2 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Tue, 20 Dec 2022 09:00:21 +0100 Subject: [PATCH 36/37] README.md --- README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 7effddd..ed2697b 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,78 @@ # FastAPI-DLS -Minimal Licencing Servie. +Minimal Delegated License Service (DLS). -## Installation +# Setup (Docker) + +**Run this on the Docker-Host** + +```shell +WORKING_DIR=/opt/docker/fastapi-dls/cert +mkdir -p $WORKING_DIR +cd $WORKING_DIR +openssl genrsa -out $WORKING_DIR/instance.private.pem 2048 +openssl rsa -in $WORKING_DIR/instance.private.pem -outform PEM -pubout -out $WORKING_DIR/instance.public.pem +openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $WORKING_DIR/webserver.key -out $WORKING_DIR/webserver.crt +docker run -e DLS_URL=`hostname -i` -e DLS_PORT=443 -p 443:443 -v $WORKING_DIR:/app/cert collinwebdesigns/fastapi-dls:latest +``` + +# Installation **The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.** -### Linux +## Linux ```shell -curl --insecure -X GET https:///client-token -o /etc/nvidia/ClientConfigToken/client_configuration_token.tok +curl --insecure -X GET https:///client-token -o /etc/nvidia/ClientConfigToken/client_configuration_token.tok service nvidia-gridd restart nvidia-smi -q | grep "License" ``` -### Windows +## Windows Download file and place it into `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`. -Now restart `NvContainerLocalSystem` service. +Now restart `NvContainerLocalSystem` service. + +# Troubleshoot + +## Linux + +Logs are available with `journalctl -u nvidia-gridd -f`. + +## Windows + +Logs are available in `C:\Users\Public\Documents\Nvidia\LoggingLog.NVDisplay.Container.exe.log`. + +# Known Issues + +## Linux + +Currently, there are no known issues. + +## Windows + +On Windows there is currently a problem returning the license. As you can see the license is installed successfully +after +a few minutes. About the time of the first *lease period* the driver gets a *Mismatch between client and server with +respect to licenses held*. + +
+ Log + +``` +Tue Dec 20 05:55:52 2022:<2>:NLS initialized +Tue Dec 20 05:55:57 2022:<2>:Mismatch between client and server with respect to licenses held. Returning the licenses +Tue Dec 20 05:55:58 2022:<2>:License returned successfully. (Info: 192.168.178.33) +Tue Dec 20 05:56:20 2022:<2>:Mismatch between client and server with respect to licenses held. Returning the licenses +Tue Dec 20 05:56:21 2022:<2>:License returned successfully. (Info: 192.168.178.33) +Tue Dec 20 05:56:46 2022:<2>:Mismatch between client and server with respect to licenses held. Returning the licenses +Tue Dec 20 05:56:47 2022:<2>:License returned successfully. (Info: 192.168.178.33) +Tue Dec 20 05:56:54 2022:<1>:License renewed successfully. (Info: 192.168.178.33, NVIDIA RTX Virtual Workstation; Expiry: 2022-12-20 5:11:54 GMT) +Tue Dec 20 05:57:17 2022:<2>:Mismatch between client and server with respect to licenses held. Returning the licenses +Tue Dec 20 05:57:18 2022:<2>:License returned successfully. (Info: 192.168.178.33) +Tue Dec 20 05:59:20 2022:<1>:License renewed successfully. (Info: 192.168.178.33, NVIDIA RTX Virtual Workstation; Expiry: 2022-12-20 5:14:20 GMT) +Tue Dec 20 06:01:45 2022:<1>:License renewed successfully. (Info: 192.168.178.33, NVIDIA RTX Virtual Workstation; Expiry: 2022-12-20 5:16:45 GMT) +Tue Dec 20 06:04:10 2022:<1>:License renewed successfully. (Info: 192.168.178.33, NVIDIA RTX Virtual Workstation; Expiry: 2022-12-20 5:19:10 GMT) +``` + +
From afa592e0ef5034660f141d5613d21b3d6a3bb938 Mon Sep 17 00:00:00 2001 From: Oscar Krause Date: Tue, 20 Dec 2022 09:01:31 +0100 Subject: [PATCH 37/37] .gitlab-ci.yml - added deploy to public registry --- .gitlab-ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b716d5a..2a07981 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -23,4 +23,8 @@ deploy: rules: - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH script: - - echo "Nothing to do ..." + - docker login -u $PUBLIC_REGISTRY_USER -p $PUBLIC_REGISTRY_TOKEN + - docker build . --tag $PUBLIC_REGISTRY_USER/${CI_PROJECT_PATH}:${CI_BUILD_REF} + - docker build . --tag $PUBLIC_REGISTRY_USER/${CI_PROJECT_PATH}:latest + - docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_PATH}:${CI_BUILD_REF} + - docker push $PUBLIC_REGISTRY_USER/${CI_PROJECT_PATH}:latest