Merge branch 'dev' into 'main'
v0.1 See merge request oscar.krause/fastapi-dls!1
This commit is contained in:
commit
a6cf5e0ac1
7
.codeclimate.yml
Normal file
7
.codeclimate.yml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
plugins:
|
||||||
|
bandit:
|
||||||
|
enabled: true
|
||||||
|
sonar-python:
|
||||||
|
enabled: true
|
||||||
|
pylint:
|
||||||
|
enabled: true
|
28
.editorconfig
Normal file
28
.editorconfig
Normal file
@ -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
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.DS_Store
|
||||||
|
venv/
|
||||||
|
.idea/
|
||||||
|
app/cert/*.*
|
30
.gitlab-ci.yml
Normal file
30
.gitlab-ci.yml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
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:
|
||||||
|
- 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
|
16
Dockerfile
Normal file
16
Dockerfile
Normal file
@ -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", "--ssl-keyfile", "/app/cert/webserver.key", "--ssl-certfile", "/app/cert/webserver.crt"]
|
77
README.md
77
README.md
@ -1,3 +1,78 @@
|
|||||||
# FastAPI-DLS
|
# FastAPI-DLS
|
||||||
|
|
||||||
Minimal Licencing Servie.
|
Minimal Delegated License Service (DLS).
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
```shell
|
||||||
|
curl --insecure -X GET https://<dls-hostname-or-ip>/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 `C:\Program Files\NVIDIA Corporation\vGPU Licensing\ClientConfigToken`.
|
||||||
|
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*.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Log</summary>
|
||||||
|
|
||||||
|
```
|
||||||
|
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)
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
9
ToDo.md
Normal file
9
ToDo.md
Normal file
@ -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
|
20
app/helper.py
Normal file
20
app/helper.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
from Crypto.PublicKey.RSA import RsaKey
|
||||||
|
|
||||||
|
|
||||||
|
def load_file(filename) -> bytes:
|
||||||
|
with open(filename, 'rb') as file:
|
||||||
|
content = file.read()
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
|
def load_key(filename) -> RsaKey:
|
||||||
|
return RSA.import_key(extern_key=load_file(filename), passphrase=None)
|
||||||
|
|
||||||
|
|
||||||
|
def private_bytes(rsa: RsaKey) -> bytes:
|
||||||
|
return rsa.export_key(format='PEM', passphrase=None, protection=None)
|
||||||
|
|
||||||
|
|
||||||
|
def public_key(rsa: RsaKey) -> bytes:
|
||||||
|
return rsa.public_key().export_key(format='PEM')
|
295
app/main.py
Normal file
295
app/main.py
Normal file
@ -0,0 +1,295 @@
|
|||||||
|
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
|
||||||
|
from datetime import datetime
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from calendar import timegm
|
||||||
|
from jose import jws, jwk, jwt
|
||||||
|
from jose.constants import ALGORITHMS
|
||||||
|
from starlette.responses import StreamingResponse, JSONResponse
|
||||||
|
|
||||||
|
from helper import load_key, private_bytes, public_key
|
||||||
|
|
||||||
|
# todo: initialize certificate (or should be done by user, and passed through "volumes"?)
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
LEASE_EXPIRE_DELTA = relativedelta(minutes=15) # days=90
|
||||||
|
|
||||||
|
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'))
|
||||||
|
|
||||||
|
|
||||||
|
@app.get('/')
|
||||||
|
async def index():
|
||||||
|
return JSONResponse({'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():
|
||||||
|
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",
|
||||||
|
"aud": "NLS Licensed Client",
|
||||||
|
"iat": timegm(cur_time.timetuple()),
|
||||||
|
"nbf": timegm(cur_time.timetuple()),
|
||||||
|
"exp": timegm(exp_time.timetuple()),
|
||||||
|
"update_mode": "ABSOLUTE",
|
||||||
|
"scope_ref_list": [str(uuid4())],
|
||||||
|
"fulfillment_class_ref_list": [],
|
||||||
|
"service_instance_configuration": {
|
||||||
|
"nls_service_instance_ref": "00000000-0000-0000-0000-000000000000",
|
||||||
|
"svc_port_set_list": [
|
||||||
|
{
|
||||||
|
"idx": 0,
|
||||||
|
"d_name": "DLS",
|
||||||
|
"svc_port_map": [
|
||||||
|
{"service": "auth", "port": DLS_PORT},
|
||||||
|
{"service": "lease", "port": DLS_PORT}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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_configuration,
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
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_origin(request: Request):
|
||||||
|
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":["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 = {
|
||||||
|
"origin_ref": j['candidate_origin_ref'],
|
||||||
|
"environment": j['environment'],
|
||||||
|
"svc_port_set_list": None,
|
||||||
|
"node_url_list": None,
|
||||||
|
"node_query_order": None,
|
||||||
|
"prompts": None,
|
||||||
|
"sync_timestamp": cur_time.isoformat()
|
||||||
|
}
|
||||||
|
return JSONResponse(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 auth_code(request: Request):
|
||||||
|
body = await request.body()
|
||||||
|
body = body.decode('utf-8')
|
||||||
|
j = json.loads(body)
|
||||||
|
# {"code_challenge":"...","origin_ref":"00112233-4455-6677-8899-aabbccddeeff"}
|
||||||
|
print(f'> [ code ]: {j}')
|
||||||
|
|
||||||
|
cur_time = datetime.utcnow()
|
||||||
|
expires = cur_time + relativedelta(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(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512)
|
||||||
|
auth_code = jws.sign(payload, key, headers=headers, algorithm='RS256')
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"auth_code": auth_code,
|
||||||
|
"sync_timestamp": cur_time.isoformat(),
|
||||||
|
"prompts": None
|
||||||
|
}
|
||||||
|
return JSONResponse(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 auth_token(request: Request):
|
||||||
|
body = await request.body()
|
||||||
|
body = body.decode('utf-8')
|
||||||
|
j = json.loads(body)
|
||||||
|
# {"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)
|
||||||
|
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 + relativedelta(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(INSTANCE_KEY_RSA.export_key().decode('utf-8'), algorithm=ALGORITHMS.RS512)
|
||||||
|
auth_token = jwt.encode(new_payload, key=key, headers=headers, algorithm='RS256')
|
||||||
|
|
||||||
|
response = {
|
||||||
|
"expires": access_expires_on.isoformat(),
|
||||||
|
"auth_token": auth_token,
|
||||||
|
"sync_timestamp": cur_time.isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post('/leasing/v1/lessor')
|
||||||
|
async def leasing_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': ['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'
|
||||||
|
lease_result_list = []
|
||||||
|
for scope_ref in j['scope_ref_list']:
|
||||||
|
lease_result_list.append({
|
||||||
|
"ordinal": 0,
|
||||||
|
"lease": {
|
||||||
|
"ref": scope_ref,
|
||||||
|
"created": cur_time.isoformat(),
|
||||||
|
"expires": (cur_time + LEASE_EXPIRE_DELTA).isoformat(),
|
||||||
|
"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.isoformat(),
|
||||||
|
"prompts": None
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
|
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
|
||||||
|
@app.get('/leasing/v1/lessor/leases')
|
||||||
|
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 = {
|
||||||
|
# GRID-Virtual-WS 2.0 CONCURRENT_COUNTED_SINGLE
|
||||||
|
"active_lease_list": [
|
||||||
|
"BE276D7B-2CDB-11EC-9838-061A22468B59"
|
||||||
|
],
|
||||||
|
"sync_timestamp": cur_time.isoformat(),
|
||||||
|
"prompts": None
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
|
# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py
|
||||||
|
@app.put('/leasing/v1/lease/{lease_ref}')
|
||||||
|
async def leasing_lease_renew(request: Request, lease_ref: str):
|
||||||
|
print(f'> [ renew ]: lease: {lease_ref}')
|
||||||
|
|
||||||
|
cur_time = datetime.utcnow()
|
||||||
|
response = {
|
||||||
|
"lease_ref": lease_ref,
|
||||||
|
"expires": (cur_time + LEASE_EXPIRE_DELTA).isoformat(),
|
||||||
|
"recommended_lease_renewal": 0.16,
|
||||||
|
"offline_lease": True,
|
||||||
|
"prompts": None,
|
||||||
|
"sync_timestamp": cur_time.isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
|
@app.delete('/leasing/v1/lessor/leases')
|
||||||
|
async def leasing_lessor_lease_remove(request: Request):
|
||||||
|
cur_time = datetime.utcnow()
|
||||||
|
response = {
|
||||||
|
"released_lease_list": None,
|
||||||
|
"release_failure_list": None,
|
||||||
|
"sync_timestamp": cur_time.isoformat(),
|
||||||
|
"prompts": None
|
||||||
|
}
|
||||||
|
return JSONResponse(response)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
###
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
|
print(f'> Starting dev-server ...')
|
||||||
|
|
||||||
|
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)
|
174
doc/Reverse Engineering Notes.md
Normal file
174
doc/Reverse Engineering Notes.md
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
# 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`
|
||||||
|
|
||||||
|
```shell
|
||||||
|
docker exec -it <container-id> /etc/dls/config/decryptor/decryptor > /tmp/private-key.pem
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
-----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**
|
||||||
|
|
||||||
|
```
|
||||||
|
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
|
||||||
|
|
||||||
|
- 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`
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
fastapi==0.88.0
|
||||||
|
uvicorn[standard]==0.20.0
|
||||||
|
python-jose==3.3.0
|
||||||
|
pycryptodome==3.16.0
|
||||||
|
python-dateutil==2.8.2
|
Loading…
Reference in New Issue
Block a user