9 Commits

Author SHA1 Message Date
Oscar Krause
7346cf6d7a Merge branch 'updates' into 'main'
updates

See merge request oscar.krause/fastapi-dls!53
2025-08-11 07:52:54 +02:00
Oscar Krause
f7d9b3574e requirements.txt updated 2025-08-11 07:24:46 +02:00
Oscar Krause
4786a345a8 added 19.x as supported and updated release matrix 2025-08-11 07:18:31 +02:00
Oscar Krause
0c5368b863 styling 2025-07-04 10:12:26 +02:00
Oscar Krause
5c54f5171f styling 2025-07-04 10:12:07 +02:00
Oscar Krause
2afd3a4eb3 requirements.txt updated 2025-07-03 09:03:53 +02:00
Oscar Krause
b5e186a58e added vgpu 18.3 2025-07-03 08:55:00 +02:00
Oscar Krause
3ebf87f01a added vgpu 18.2 2025-06-02 07:03:06 +02:00
Oscar Krause
b8ec5e11e2 updated urls 2025-05-21 10:10:49 +02:00
10 changed files with 60 additions and 60 deletions

View File

@@ -2,7 +2,7 @@ Package: fastapi-dls
Version: 0.0
Architecture: all
Maintainer: Oscar Krause oscar.krause@collinwebdesigns.de
Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-jwt, python3-sqlalchemy, python3-cryptography, python3-markdown, uvicorn, openssl
Depends: python3, python3-fastapi, python3-uvicorn, python3-dotenv, python3-dateutil, python3-jose, python3-sqlalchemy, python3-cryptography, python3-markdown, uvicorn, openssl
Recommends: curl
Installed-Size: 10240
Homepage: https://git.collinwebdesigns.de/oscar.krause/fastapi-dls

View File

@@ -1,7 +1,7 @@
# https://packages.debian.org/hu/
fastapi==0.92.0
uvicorn[standard]==0.17.6
pyjwt==2.10.1
python-jose[cryptography]==3.3.0
cryptography==38.0.4
python-dateutil==2.8.2
sqlalchemy==1.4.46

View File

@@ -1,7 +1,7 @@
# https://packages.ubuntu.com
fastapi==0.101.0
uvicorn[standard]==0.27.1
pyjwt==2.10.1
python-jose[cryptography]==3.3.0
cryptography==41.0.7
python-dateutil==2.8.2
sqlalchemy==1.4.50

View File

@@ -1,7 +1,7 @@
# https://packages.ubuntu.com
fastapi==0.110.3
uvicorn[standard]==0.30.3
pyjwt==2.10.1
python-jose[cryptography]==3.3.0
cryptography==42.0.5
python-dateutil==2.9.0
sqlalchemy==2.0.32

View File

@@ -8,7 +8,7 @@ pkgdesc='NVIDIA DLS server implementation with FastAPI'
arch=('any')
url='https://git.collinwebdesigns.de/oscar.krause/fastapi-dls'
license=('MIT')
depends=('python' 'python-pyjwt' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-cryptography' 'uvicorn' 'python-markdown' 'openssl')
depends=('python' 'python-jose' 'python-starlette' 'python-httpx' 'python-fastapi' 'python-dotenv' 'python-dateutil' 'python-sqlalchemy' 'python-cryptography' 'uvicorn' 'python-markdown' 'openssl')
provider=("$pkgname")
install="$pkgname.install"
backup=('etc/default/fastapi-dls')

View File

@@ -172,12 +172,12 @@ test:apt:
parallel:
matrix:
- IMAGE:
- debian:trixie-slim # EOL: t.b.a.; "python3-jose" not available, but "python3-josepy" or "python3-jwt"
# - debian:trixie-slim # EOL: t.b.a.; "python3-jose" not available, but "python3-josepy"
- debian:bookworm-slim # EOL: June 06, 2026
- debian:bookworm-slim # EOL: June 06, 2026
- debian:bullseye-slim # EOL: June 06, 2026
- ubuntu:24.04 # EOL: April 2036
- ubuntu:24.10 # EOL: t.b.a.; "python3-jose" not available, but "python3-josepy" or "python3-jwt"
- ubuntu:25.04 # EOL: t.b.a.; "python3-jose" not available, but "python3-josepy" or "python3-jwt"
# - ubuntu:24.10 # EOL: t.b.a.; "python3-jose" not available, but "python3-josepy"
# - ubuntu:25.04 # EOL: t.b.a.; "python3-jose" not available, but "python3-josepy"
needs:
- job: build:apt
artifacts: true

View File

@@ -4,13 +4,13 @@ Minimal Delegated License Service (DLS).
> [!warning] Branch support
> FastAPI-DLS Version 1.x supports up to **`17.x`** releases. \
> FastAPI-DLS Version 2.x is backwards compatible to `17.x` and supports **`18.x`** releases in combination
> with [gridd-unlock-patcher](https://git.collinwebdesigns.de/oscar.krause/gridd-unlock-patcher).
> FastAPI-DLS Version 2.x is backwards compatible to `17.x` and supports **`18.x`**, **`19.x`**, releases in combination
> with [gridd-unlock-patcher](https://git.collinwebdesigns.de/vgpu/gridd-unlock-patcher).
> Other combinations of FastAPI-DLS and Driver-Branches may work but are not tested.
> [!note] Compatibility
> Compatibility tested with official NLS 2.0.1, 2.1.0, 3.1.0, 3.3.1, 3.4.0. For Driver compatibility
> see [compatibility matrix](#vgpu-software-compatibility-matrix).
> Compatibility tested with official NLS 2.0.1, 2.1.0, 3.1.0, 3.3.1, 3.4.0. **For Driver compatibility
> see [compatibility matrix](#vgpu-software-compatibility-matrix)**.
This service can be used without internet connection.
Only the clients need a connection to this service on configured port.
@@ -336,7 +336,10 @@ Successful tested with (**LTS Version**):
Not working with:
- Debian 11 (Bullseye) and lower (missing `python-jose` dependency)
- Debian 13 (Trixie) (missing `python-jose` dependency)
- Ubuntu 22.04 (Jammy Jellyfish) (not supported as for 15.01.2023 due to [fastapi - uvicorn version missmatch](https://bugs.launchpad.net/ubuntu/+source/fastapi/+bug/1970557))
- Ubuntu 24.10 (Oracular Oriole) (missing `python-jose` dependency)
**Run this on your server instance**
@@ -669,7 +672,8 @@ Shows current runtime environment variables and their values.
**`GET /-/config/root-certificate`**
Returns the Root-Certificate Certificate which is used. This is required for patching `nvidia-gridd` on 18.x releases.
Returns the Root-Certificate Certificate which is used.
This is required for patching `nvidia-gridd` on `18.x`, `19.x` releases.
**`GET /-/readme`**
@@ -880,14 +884,16 @@ The error message can safely be ignored (since we have no license limitation :P)
# vGPU Software Compatibility Matrix
<details>
<summary>Show Table</summary>
Successfully tested with this package versions.
<summary>Successfully tested with this package versions: Show Table</summary>
| FastAPI-DLS Version | vGPU Suftware | Driver Branch | Linux vGPU Manager | Linux Driver | Windows Driver | Release Date | EOL Date |
|---------------------|:-------------:|:-------------:|--------------------|--------------|----------------|--------------:|--------------:|
| `2.x` | `18.1` | **R570** | `570.133.08` | `570.133.07` | `572.83` | April 2025 | March 2026 |
| | `18.0` | **R570** | `570.124.03` | `570.124.06` | `572.60` | March 2025 | March 2026 |
| `2.x` | `19.0` | **R580** | `580.65.05` | `580.65.06` | `580.88` | August 2025 | July 2028 |
| `2.x` | `18.4` | **R570** | `570.172.07` | `570.172.08` | `573.48` | July 2025 | March 2026 |
| | `18.3` | **R570** | `570.158.02` | `570.158.01` | `573.36` | June 2025 | |
| | `18.2` | **R570** | `570.148.06` | `570.148.08` | `573.07` | May 2025 | |
| | `18.1` | **R570** | `570.133.08` | `570.133.07` | `572.83` | April 2025 | |
| | `18.0` | **R570** | `570.124.03` | `570.124.06` | `572.60` | March 2025 | |
| `1.x` & `2.x` | `17.6` | **R550** | `550.163.02` | `550.63.01` | `553.74` | April 2025 | June 2025 |
| | `17.5` | | `550.144.02` | `550.144.03` | `553.62` | January 2025 | |
| | `17.4` | | `550.127.06` | `550.127.05` | `553.24` | October 2024 | |
@@ -895,7 +901,7 @@ Successfully tested with this package versions.
| | `17.2` | | `550.90.05` | `550.90.07` | `552.55` | June 2024 | |
| | `17.1` | | `550.54.16` | `550.54.15` | `551.78` | March 2024 | |
| | `17.0` | **R550** | `550.54.10` | `550.54.14` | `551.61` | February 2024 | |
| `1.x` | `16.10` | **R535** | `535.247.02` | `535.247.01` | `539.28` | April 2025 | July 2026 |
| `1.x` | `16.11` | **R535** | `535.261.04` | `535.261.03` | `539.41` | July 2025 | July 2026 |
| `1.x` | `15.4` | **R525** | `525.147.01` | `525.147.05` | `529.19` | June 2023 | December 2023 |
| `1.x` | `14.4` | **R510** | `510.108.03` | `510.108.03` | `514.08` | December 2022 | February 2023 |
@@ -921,6 +927,6 @@ Special thanks to:
- `Krutav Shah` who wrote the [vGPU_Unlock Wiki](https://docs.google.com/document/d/1pzrWJ9h-zANCtyqRgS7Vzla0Y8Ea2-5z2HEi4X75d2Q/)
- `Wim van 't Hoog` for the [Proxmox All-In-One Installer Script](https://wvthoog.nl/proxmox-vgpu-v3/)
- `mrzenc` who wrote [fastapi-dls-nixos](https://github.com/mrzenc/fastapi-dls-nixos)
- `electricsheep49` who wrote [gridd-unlock-patcher](https://git.collinwebdesigns.de/oscar.krause/gridd-unlock-patcher)
- `electricsheep49` who wrote [gridd-unlock-patcher](https://git.collinwebdesigns.de/vgpu/gridd-unlock-patcher)
And thanks to all people who contributed to all these libraries!

View File

@@ -15,7 +15,8 @@ from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.requests import Request
from fastapi.responses import Response, RedirectResponse, StreamingResponse
import jwt
from jose import jws, jwk, jwt, JWTError
from jose.constants import ALGORITHMS
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from starlette.middleware.cors import CORSMiddleware
@@ -62,8 +63,8 @@ my_si_certificate = Cert.from_file(ca_setup.si_certificate_filename)
my_si_private_key = PrivateKey.from_file(ca_setup.si_private_key_filename)
my_si_public_key = my_si_private_key.public_key()
jwt_encode_key = my_si_private_key.pem() # todo: replace directly in code
jwt_decode_key = my_si_private_key.public_key().pem() # todo: replace directly in code
jwt_encode_key = jwk.construct(my_si_private_key.pem(), algorithm=ALGORITHMS.RS256)
jwt_decode_key = jwk.construct(my_si_private_key.public_key().pem(), algorithm=ALGORITHMS.RS256)
# Logging
LOG_LEVEL = logging.DEBUG if DEBUG else logging.INFO
@@ -113,9 +114,7 @@ app.add_middleware(
def __get_token(request: Request) -> dict:
authorization_header = request.headers.get('authorization')
token = authorization_header.split(' ')[1]
# return jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
return jwt.decode(jwt=token, key=jwt_decode_key, algorithms=['RS256'], options={'verify_aud': False})
return jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
# Endpoints
@@ -296,11 +295,9 @@ async def _client_token():
},
}
# content = jws.sign(payload, key=jwt_encode_key, headers=None, algorithm=ALGORITHMS.RS256)
content = jwt.encode(payload=payload, key=jwt_encode_key, headers=None, algorithm='RS256')
content = jws.sign(payload, key=jwt_encode_key, headers=None, algorithm=ALGORITHMS.RS256)
# response = StreamingResponse(iter([content]), media_type="text/plain")
response = StreamingResponse(iter(content), media_type="text/plain")
response = StreamingResponse(iter([content]), media_type="text/plain")
filename = f'client_configuration_token_{datetime.now().strftime("%d-%m-%y-%H-%M-%S")}.tok'
response.headers["Content-Disposition"] = f'attachment; filename={filename}'
@@ -389,8 +386,7 @@ async def auth_v1_code(request: Request):
'kid': SITE_KEY_XID
}
# auth_code = jws.sign(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm=ALGORITHMS.RS256)
auth_code = jwt.encode(payload=payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm='RS256')
auth_code = jws.sign(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm=ALGORITHMS.RS256)
response = {
"auth_code": auth_code,
@@ -408,9 +404,8 @@ async def auth_v1_token(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
try:
#payload = jwt.decode(token=j.get('auth_code'), key=jwt_decode_key, algorithms=ALGORITHMS.RS256)
payload = jwt.decode(jwt=j.get('auth_code'), key=jwt_decode_key, algorithms=['RS256'])
except jwt.PyJWTError as e:
payload = jwt.decode(token=j.get('auth_code'), key=jwt_decode_key, algorithms=ALGORITHMS.RS256)
except JWTError as e:
response = {'status': 400, 'title': 'invalid token', 'detail': str(e)}
return Response(content=json_dumps(response), media_type='application/json', status_code=400)
@@ -436,7 +431,7 @@ async def auth_v1_token(request: Request):
'origin_ref': origin_ref,
}
auth_token = jwt.encode(payload=new_payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm='RS256')
auth_token = jwt.encode(new_payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm=ALGORITHMS.RS256)
response = {
"auth_token": auth_token,
@@ -475,9 +470,8 @@ async def leasing_v1_config_token(request: Request):
},
}
# my_jwt_encode_key = jwk.construct(my_si_private_key.pem().decode('utf-8'), algorithm=ALGORITHMS.RS256)
# config_token = jws.sign(payload, key=my_jwt_encode_key, headers=None, algorithm=ALGORITHMS.RS256)
config_token = jwt.encode(payload=payload, key=jwt_encode_key, headers=None, algorithm='RS256')
my_jwt_encode_key = jwk.construct(my_si_private_key.pem().decode('utf-8'), algorithm=ALGORITHMS.RS256)
config_token = jws.sign(payload, key=my_jwt_encode_key, headers=None, algorithm=ALGORITHMS.RS256)
response_ca_chain = my_ca_certificate.pem().decode('utf-8').strip()
@@ -708,7 +702,7 @@ async def leasing_v1_lessor_shutdown(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
token = j.get('token')
token = jwt.decode(jwt=token, key=jwt_decode_key, algorithms='RS256', options={'verify_aud': False})
token = jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
origin_ref = token.get('origin_ref')
released_lease_list = list(map(lambda x: x.lease_ref, Lease.find_by_origin_ref(db, origin_ref)))

View File

@@ -1,8 +1,8 @@
fastapi==0.115.12
uvicorn[standard]==0.34.2
pyjwt==2.10.1
cryptography==44.0.3
fastapi==0.116.1
uvicorn[standard]==0.35.0
python-jose[cryptography]==3.5.0
cryptography==45.0.6
python-dateutil==2.9.0
sqlalchemy==2.0.41
markdown==3.8
python-dotenv==1.1.0
sqlalchemy==2.0.42
markdown==3.8.2
python-dotenv==1.1.1

View File

@@ -4,13 +4,13 @@ from base64 import b64encode as b64enc
from calendar import timegm
from datetime import datetime, UTC
from hashlib import sha256
from json import loads as json_loads, dumps as json_dumps
from uuid import uuid4, UUID
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.hashes import SHA256
from dateutil.relativedelta import relativedelta
import jwt
from jose import jwt, jwk, jws
from jose.constants import ALGORITHMS
from starlette.testclient import TestClient
# add relative path to use packages as they were in the app/ dir
@@ -38,12 +38,12 @@ my_si_public_key = my_si_private_key.public_key()
my_si_public_key_as_pem = my_si_private_key.public_key().pem()
my_si_certificate = Cert.from_file(ca_setup.si_certificate_filename)
jwt_encode_key = my_si_private_key.pem()
jwt_decode_key = my_si_private_key.public_key().pem()
jwt_encode_key = jwk.construct(my_si_private_key_as_pem, algorithm=ALGORITHMS.RS256)
jwt_decode_key = jwk.construct(my_si_public_key_as_pem, algorithm=ALGORITHMS.RS256)
def __bearer_token(origin_ref: str) -> str:
# token = jwt.encode({"origin_ref": origin_ref}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
token = jwt.encode(payload={"origin_ref": origin_ref}, key=jwt_encode_key, algorithm='RS256')
token = jwt.encode({"origin_ref": origin_ref}, key=jwt_encode_key, algorithm=ALGORITHMS.RS256)
token = f'Bearer {token}'
return token
@@ -145,12 +145,12 @@ def test_config_token():
assert nv_si_certificate.public_key().mod() == nv_response_public_key.get('mod')[0]
assert nv_si_certificate.authority_key_identifier() == nv_ca_chain.subject_key_identifier()
# nv_jwt_decode_key = jwk.construct(nv_response_public_cert, algorithm=ALGORITHMS.RS256)
nv_jwt_decode_key = jwk.construct(nv_response_public_cert, algorithm=ALGORITHMS.RS256)
nv_response_config_token = response.json().get('configToken')
#payload = jws.verify(nv_response_config_token, key=nv_jwt_decode_key, algorithms=ALGORITHMS.RS256)
payload = jwt.decode(jwt=nv_response_config_token, key=nv_si_certificate.public_key().pem(), algorithms=['RS256'], options={'verify_signature': False})
payload = jws.verify(nv_response_config_token, key=nv_jwt_decode_key, algorithms=ALGORITHMS.RS256)
payload = json.loads(payload)
assert payload.get('iss') == 'NLS Service Instance'
assert payload.get('aud') == 'NLS Licensed Client'
assert payload.get('service_instance_ref') == INSTANCE_REF
@@ -230,7 +230,7 @@ def test_auth_v1_code():
response = client.post('/auth/v1/code', json=payload)
assert response.status_code == 200
payload = jwt.decode(response.json().get('auth_code'), key=my_si_public_key_as_pem, algorithms=['RS256'])
payload = jwt.get_unverified_claims(token=response.json().get('auth_code'))
assert payload.get('origin_ref') == ORIGIN_REF
@@ -247,7 +247,7 @@ def test_auth_v1_token():
"kid": "00000000-0000-0000-0000-000000000000"
}
payload = {
"auth_code": jwt.encode(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm='RS256'),
"auth_code": jwt.encode(payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm=ALGORITHMS.RS256),
"code_verifier": SECRET,
}
@@ -255,7 +255,7 @@ def test_auth_v1_token():
assert response.status_code == 200
token = response.json().get('auth_token')
payload = jwt.decode(token, key=jwt_decode_key, algorithms=['RS256'], options={'verify_signature': False})
payload = jwt.decode(token=token, key=jwt_decode_key, algorithms=ALGORITHMS.RS256, options={'verify_aud': False})
assert payload.get('origin_ref') == ORIGIN_REF