Compare commits

...

95 Commits

Author SHA1 Message Date
b577edcf67 release registry url updated 2025-04-23 22:19:27 +02:00
8e3e49fc36 ci fixes 2025-04-23 22:02:04 +02:00
2638f67cd9 typos 2025-04-23 21:55:45 +02:00
60a6c83a3d Merge branch 'v18.x-support' into 'main'
v18.x support / NLS 3.4.x compatibility

See merge request oscar.krause/fastapi-dls!46
2025-04-23 21:54:43 +02:00
ae03867502 updated compatibility matrix 2025-04-23 10:35:07 +02:00
295e3c9482 fixes 2025-04-22 20:42:11 +02:00
3961acf231 fixes 2025-04-22 15:07:29 +02:00
04914740a4 improved tests 2025-04-22 14:38:17 +02:00
6af9cd04c9 added variable for custom cert path 2025-04-22 14:38:05 +02:00
29268b1658 code styling and tests 2025-04-22 14:16:30 +02:00
938a112b8a fixes 2025-04-22 14:00:56 +02:00
16870e9d67 code styling 2025-04-22 12:35:34 +02:00
55b7437fe7 fixes 2025-04-22 11:29:31 +02:00
e7e007a45f fixes 2025-04-22 11:22:57 +02:00
161a1430cf code styling 2025-04-22 11:06:54 +02:00
1ccb203b25 code styling 2025-04-22 11:05:03 +02:00
6c1a8d42dc fixes 2025-04-22 11:04:32 +02:00
d248496f34 fixes 2025-04-22 10:45:41 +02:00
fd1babaca5 fixes 2025-04-22 10:10:32 +02:00
cd9c655d65 fixes 2025-04-22 09:45:58 +02:00
6ed4bdfe6f fixes 2025-04-22 09:05:18 +02:00
e1ae757a50 updated tests 2025-04-22 08:53:30 +02:00
b0ca5d7ab5 fixes 2025-04-22 08:06:17 +02:00
14f8b54752 test with "76 chars per line" 2025-04-22 07:32:30 +02:00
dc783e6518 typos 2025-04-22 07:32:05 +02:00
3666e22707 fixes 2025-04-21 21:57:40 +02:00
6b54d4794b .gitlab-ci.yml bearbeiten 2025-04-16 15:07:28 +02:00
f38378bbc8 updated credits 2025-04-16 15:05:45 +02:00
cd4c3d379a code styling 2025-04-16 15:01:14 +02:00
59645d1daf Merge branch 'main' into v18.x-support
# Conflicts:
#	app/util.py
2025-04-16 15:00:33 +02:00
9605ba3eee Merge branch 'dev' into 'main'
Dev

See merge request oscar.krause/fastapi-dls!51
2025-04-16 14:43:58 +02:00
cd5c2a6cb1 code styling 2025-04-16 14:18:04 +02:00
1d3255188e code styling 2025-04-16 14:12:40 +02:00
cea7a01b54 code styling 2025-04-16 14:12:15 +02:00
ff2fbaf83f code styling 2025-04-16 14:11:04 +02:00
9f417b61a9 fixes 2025-04-16 14:07:18 +02:00
4f77200628 code styling 2025-04-16 14:07:18 +02:00
20b03446dc Merge branch 'main' into 'dev'
# Conflicts:
#   .gitlab-ci.yml
2025-04-16 13:57:51 +02:00
df506e8591 removed unsupported python versions 2025-04-16 13:56:36 +02:00
3fe3429986 added some python versions and added EOL 2025-04-16 12:54:24 +02:00
a996504c50 test python3.13 and 3.11 2025-04-16 12:44:58 +02:00
5d2bff88d8 fixes 2025-04-16 12:35:24 +02:00
67f2d18a95 requirements.txt updated 2025-04-16 12:25:37 +02:00
52cd34cb5c ci improvements 2025-04-16 12:24:19 +02:00
6fb03309a5 ci improvements 2025-04-16 12:23:21 +02:00
477e5abbca refactored test pipeline to test different python versions 2025-04-16 12:12:23 +02:00
e00e7569eb removed todo 2025-04-16 12:01:19 +02:00
4650e18821 added todo 2025-04-16 12:00:21 +02:00
383c7f8684 updated compatibility 2025-04-16 11:59:53 +02:00
9712a84a00 code styling 2025-04-16 09:30:04 +02:00
133b1e24e2 code styling 2025-04-16 09:29:10 +02:00
8f4d2e6086 updated versions matrix and infos 2025-04-16 09:26:52 +02:00
389b36fcb8 removed any 'instance.*.pem' reference 2025-04-16 09:19:17 +02:00
a767e73ca6 code styling 2025-04-16 09:03:27 +02:00
31957ec6d7 added '/-/config/root-ca' endpoint 2025-04-16 08:37:28 +02:00
da31c5f0a7 fixes 2025-04-16 08:10:49 +02:00
1265be5fbe fixes 2025-04-16 08:05:58 +02:00
e23912c49a code refactorings - removed INSTANCE_KEY_RSA and INSTANCE_KEY_PUB from configuration and implemented CASetup instead 2025-04-16 07:54:23 +02:00
b9e78dbeeb response fixes 2025-04-15 07:58:55 +02:00
33a561793e test case sensitive headers 2025-04-15 07:35:41 +02:00
445a303955 added signature tests 2025-04-14 21:38:20 +02:00
79f1015a86 code styling 2025-04-14 21:38:20 +02:00
e2c4e45764 Datei .gitlab-ci.yml aktualisieren 2025-04-14 20:45:12 +02:00
7b2d61b329 fixed signature 2025-04-14 20:29:50 +02:00
f9fccd5502 implemented product_mapping support 2025-04-14 20:29:50 +02:00
70cac5bbf3 test "X-NLS-Signature" upper/lowercase 2025-04-14 20:29:50 +02:00
fbb73c73ab changed "feature_name" 2025-04-14 20:29:50 +02:00
94a7772c7b updated attributes to match nvidia-nls response order 2025-04-14 20:29:50 +02:00
54d38953bf x-nls-signature 2025-04-14 20:29:50 +02:00
3871dfe6a6 test "x-nls-signature" 2025-04-14 20:29:50 +02:00
e69e93bcb4 fixed missing code 2025-04-14 20:29:50 +02:00
d7c29f834c test with "X-NLS-Signature" 2025-04-14 20:29:50 +02:00
685e1ce0bb implemented client_challenge on lease apis 2025-04-14 20:29:50 +02:00
d8cddd4b29 set "offline_lease" to false 2025-04-14 20:29:50 +02:00
60f7e40ca4 added EMPTY (!!!) X-NLS-Signature response header 2025-04-14 20:29:50 +02:00
672ddb16c7 added debug headers 2025-04-14 20:29:50 +02:00
beebf9e812 code styling 2025-04-14 20:29:50 +02:00
743c702921 added new "protocol_version" to client-token 2025-04-14 20:29:50 +02:00
601d733add fixes 2025-04-14 20:29:50 +02:00
2ffee4aee1 also debug response 2025-04-14 20:29:50 +02:00
5eb0c55f3f fixed datetime format 2025-04-14 20:29:50 +02:00
c7e8414934 added missing lessor attributes 2025-04-14 20:29:50 +02:00
30ef56f956 fixes 2025-04-14 20:29:50 +02:00
1908b44bed added debugging 2025-04-14 20:29:50 +02:00
0982106b4f updated new responses for 18.x drivers 2025-04-14 20:29:50 +02:00
c15cdee610 created "init_config_token_demo" 2025-04-14 20:29:50 +02:00
7ce79ec95b added test and code for /leasing/v1/config-token
ref. https://git.collinwebdesigns.de/nvidia/nls/-/blob/main/src/test/test_config_token.py
2025-04-14 20:29:50 +02:00
88fbd08610 implemented initial endpoint for /leasing/v1/config-token 2025-04-14 20:29:50 +02:00
e438de6281 added logging 2025-04-14 20:29:50 +02:00
d71b66d192 added debugging 2025-04-14 20:29:50 +02:00
61c9e47237 added endpoint '/leasing/v1/config-token' 2025-04-14 20:29:50 +02:00
9e5b4f5a42 Datei .gitlab-ci.yml aktualisieren 2025-04-14 20:13:02 +02:00
f9c7475250 ci fixes 2025-04-14 13:37:27 +02:00
00dafdc63a do not run duplicated pipeline on feature-branches 2025-04-14 11:17:25 +02:00
a8c1cdf095 updated create_driver_matrix_json.py 2025-04-11 14:10:26 +02:00
16 changed files with 1413 additions and 276 deletions

View File

@ -21,7 +21,3 @@ DATABASE=sqlite:////etc/fastapi-dls/db.sqlite
#SITE_KEY_XID="00000000-0000-0000-0000-000000000000"
#INSTANCE_REF="10000000-0000-0000-0000-000000000001"
#ALLOTMENT_REF="20000000-0000-0000-0000-000000000001"
# Site-wide signing keys
INSTANCE_KEY_RSA=/etc/fastapi-dls/instance.private.pem
INSTANCE_KEY_PUB=/etc/fastapi-dls/instance.public.pem

View File

@ -3,14 +3,6 @@
WORKING_DIR=/usr/share/fastapi-dls
CONFIG_DIR=/etc/fastapi-dls
if [ ! -f $CONFIG_DIR/instance.private.pem ]; then
echo "> Create dls-instance keypair ..."
openssl genrsa -out $CONFIG_DIR/instance.private.pem 2048
openssl rsa -in $CONFIG_DIR/instance.private.pem -outform PEM -pubout -out $CONFIG_DIR/instance.public.pem
else
echo "> Create dls-instance keypair skipped! (exists)"
fi
while true; do
[ -f $CONFIG_DIR/webserver.key ] && default_answer="N" || default_answer="Y"
[ $default_answer == "Y" ] && V="Y/n" || V="y/N"

View File

@ -17,7 +17,7 @@ source=("git+file://${CI_PROJECT_DIR}"
"$pkgname.service"
"$pkgname.tmpfiles")
sha256sums=('SKIP'
'fbd015449a30c0ae82733289a56eb98151dcfab66c91b37fe8e202e39f7a5edb'
'a4776a0ae4671751065bf3e98aa707030b8b5ffe42dde942c51050dab5028c54'
'2719338541104c537453a65261c012dda58e1dbee99154cf4f33b526ee6ca22e'
'3dc60140c08122a8ec0e7fa7f0937eb8c1288058890ba09478420fc30ce9e30c')
@ -30,8 +30,6 @@ pkgver() {
check() {
cd "$srcdir/$pkgname/test"
mkdir "$srcdir/$pkgname/app/cert"
openssl genrsa -out "$srcdir/$pkgname/app/cert/instance.private.pem" 2048
openssl rsa -in "$srcdir/$pkgname/app/cert/instance.private.pem" -outform PEM -pubout -out "$srcdir/$pkgname/app/cert/instance.public.pem"
python "$srcdir/$pkgname/test/main.py"
rm -rf "$srcdir/$pkgname/app/cert"
}

View File

@ -19,10 +19,6 @@ DATABASE="sqlite:////var/lib/fastapi-dls/db.sqlite"
SITE_KEY_XID="<<sitekey>>"
INSTANCE_REF="<<instanceref>>"
# Site-wide signing keys
INSTANCE_KEY_RSA="/var/lib/fastapi-dls/instance.private.pem"
INSTANCE_KEY_PUB="/var/lib/fastapi-dls/instance.public.pem"
# TLS certificate
INSTANCE_SSL_CERT="/var/lib/fastapi-dls/cert/webserver.crt"
INSTANCE_SSL_KEY="/var/lib/fastapi-dls/cert/webserver.key"

View File

@ -7,8 +7,4 @@ post_install() {
echo
echo 'A valid HTTPS certificate needs to be installed to /var/lib/fastapi-dls/cert/webserver.{crt,key}'
echo 'A self-signed certificate can be generated with: openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout /var/lib/fastapi-dls/cert/webserver.key -out /var/lib/fastapi-dls/cert/webserver.crt'
echo
echo 'The signing keys for your instance need to be generated as well. Generate them with these commands:'
echo 'openssl genrsa -out /var/lib/fastapi-dls/instance.private.pem 2048'
echo 'openssl rsa -in /var/lib/fastapi-dls/instance.private.pem -outform PEM -pubout -out /var/lib/fastapi-dls/instance.public.pem'
}

View File

@ -18,9 +18,6 @@ Make sure you create these certificates before starting the container for the fi
WORKING_DIR=/mnt/user/appdata/fastapi-dls/cert&#xD;
mkdir -p $WORKING_DIR&#xD;
cd $WORKING_DIR&#xD;
# create instance private and public key for singing JWT's&#xD;
openssl genrsa -out $WORKING_DIR/instance.private.pem 2048 &#xD;
openssl rsa -in $WORKING_DIR/instance.private.pem -outform PEM -pubout -out $WORKING_DIR/instance.public.pem&#xD;
# create ssl certificate for integrated webserver (uvicorn) - because clients rely on ssl&#xD;
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $WORKING_DIR/webserver.key -out $WORKING_DIR/webserver.crt&#xD;
```&#xD;

View File

@ -16,12 +16,12 @@ build:docker:
interruptible: true
stage: build
rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
# deployment is in "deploy:docker:"
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
- app/**/*
- Dockerfile
- requirements.txt
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
tags: [ docker ]
before_script:
- docker buildx inspect
@ -44,16 +44,13 @@ build:apt:
- if: $CI_COMMIT_TAG
variables:
VERSION: $CI_COMMIT_REF_NAME
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
- if: ($CI_PIPELINE_SOURCE == 'merge_request_event') || ($CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH)
changes:
- app/**/*
- .DEBIAN/**/*
- .gitlab-ci.yml
variables:
VERSION: "0.0.1"
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
variables:
VERSION: "0.0.1"
before_script:
- echo -e "VERSION=$VERSION\nCOMMIT=$CI_COMMIT_SHA" > version.env
# install build dependencies
@ -94,16 +91,13 @@ build:pacman:
- if: $CI_COMMIT_TAG
variables:
VERSION: $CI_COMMIT_REF_NAME
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
- if: ($CI_PIPELINE_SOURCE == 'merge_request_event') || ($CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH)
changes:
- app/**/*
- .PKGBUILD/**/*
- .gitlab-ci.yml
variables:
VERSION: "0.0.1"
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
variables:
VERSION: "0.0.1"
before_script:
#- echo -e "VERSION=$VERSION\nCOMMIT=$CI_COMMIT_SHA" > version.env
# install build dependencies
@ -126,13 +120,12 @@ build:pacman:
paths:
- "*.pkg.tar.zst"
test:
image: python:3.12-slim-bookworm
test:python:
image: $IMAGE
stage: test
interruptible: true
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
changes:
@ -142,37 +135,49 @@ test:
DATABASE: sqlite:///../app/db.sqlite
parallel:
matrix:
- REQUIREMENTS:
- 'requirements.txt'
# - '.DEBIAN/requirements-bookworm-12.txt'
# - '.DEBIAN/requirements-ubuntu-24.04.txt'
# - '.DEBIAN/requirements-ubuntu-24.10.txt'
- IMAGE:
# https://devguide.python.org/versions/#supported-versions
# - python:3.14-rc-alpine # EOL 2030-10 => uvicorn does not support 3.14 yet
- python:3.13-alpine # EOL 2029-10
- python:3.12-alpine # EOL 2028-10
- python:3.11-alpine # EOL 2027-10
# - python:3.10-alpine # EOL 2026-10 => ImportError: cannot import name 'UTC' from 'datetime'
# - python:3.9-alpine # EOL 2025-10 => ImportError: cannot import name 'UTC' from 'datetime'
before_script:
- apt-get update && apt-get install -y python3-dev python3-pip python3-venv gcc
- apk --no-cache add openssl
- python3 -m venv venv
- source venv/bin/activate
- pip install --upgrade pip
- pip install -r $REQUIREMENTS
- pip install -r requirements.txt
- pip install pytest pytest-cov pytest-custom_exit_code httpx
- mkdir -p app/cert
- openssl genrsa -out app/cert/instance.private.pem 2048
- openssl rsa -in app/cert/instance.private.pem -outform PEM -pubout -out app/cert/instance.public.pem
- cd test
script:
- python -m pytest main.py --junitxml=report.xml
artifacts:
reports:
dotenv: version.env
junit: ['**/report.xml']
.test:apt:
test:apt:
image: $IMAGE
stage: test
rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: ($CI_PIPELINE_SOURCE == 'merge_request_event') || ($CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH)
changes:
- app/**/*
- .DEBIAN/**/*
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- .gitlab-ci.yml
variables:
VERSION: "0.0.1"
parallel:
matrix:
- IMAGE:
- debian:trixie-slim # EOL: t.b.a.
- debian:bookworm-slim # EOL: June 06, 2026
- debian:bookworm-slim # EOL: June 06, 2026
- ubuntu:24.04 # EOL: April 2036
- ubuntu:24.10
needs:
- job: build:apt
artifacts: true
@ -204,24 +209,15 @@ test:
- apt-get purge -qq -y fastapi-dls
- apt-get autoremove -qq -y && apt-get clean -qq
test:apt:
extends: .test:apt
image: $IMAGE
parallel:
matrix:
- IMAGE:
- debian:bookworm-slim # EOL: June 06, 2026
- ubuntu:24.04 # EOL: April 2036
- ubuntu:24.10
test:pacman:archlinux:
image: archlinux:base
rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
changes:
- app/**/*
- .PKGBUILD/**/*
- if: $CI_PIPELINE_SOURCE == 'merge_request_event'
- .gitlab-ci.yml
needs:
- job: build:pacman
artifacts: true
@ -267,8 +263,6 @@ test_coverage:
- pip install -r requirements.txt
- pip install pytest pytest-cov pytest-custom_exit_code httpx
- mkdir -p app/cert
- openssl genrsa -out app/cert/instance.private.pem 2048
- openssl rsa -in app/cert/instance.private.pem -outform PEM -pubout -out app/cert/instance.public.pem
- cd test
script:
- coverage run -m pytest main.py --junitxml=report.xml --suppress-no-test-exit-code
@ -296,15 +290,12 @@ gemnasium-python-dependency_scanning:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
.deploy:
rules:
- if: $CI_COMMIT_TAG
deploy:docker:
extends: .deploy
image: docker:dind
stage: deploy
tags: [ docker ]
rules:
- if: $CI_COMMIT_TAG
before_script:
- echo "Building docker image for commit $CI_COMMIT_SHA with version $CI_COMMIT_REF_NAME"
- docker buildx inspect
@ -323,9 +314,10 @@ deploy:docker:
deploy:apt:
# doc: https://git.collinwebdesigns.de/help/user/packages/debian_repository/index.md#install-a-package
extends: .deploy
image: debian:bookworm-slim
stage: deploy
rules:
- if: $CI_COMMIT_TAG
needs:
- job: build:apt
artifacts: true
@ -362,9 +354,10 @@ deploy:apt:
- 'curl --header "JOB-TOKEN: $CI_JOB_TOKEN" --upload-file ${EXPORT_NAME} "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}/${EXPORT_NAME}"'
deploy:pacman:
extends: .deploy
image: archlinux:base-devel
stage: deploy
rules:
- if: $CI_COMMIT_TAG
needs:
- job: build:pacman
artifacts: true
@ -385,7 +378,7 @@ deploy:pacman:
release:
image: registry.gitlab.com/gitlab-org/release-cli:latest
stage: .post
needs: [ test ]
needs: [ deploy:docker, deploy:apt, deploy:pacman ]
rules:
- if: $CI_COMMIT_TAG
script:
@ -400,4 +393,4 @@ release:
- name: 'Package Registry'
url: 'https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/-/packages'
- name: 'Container Registry'
url: 'https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry/40'
url: 'https://git.collinwebdesigns.de/oscar.krause/fastapi-dls/container_registry/70'

View File

@ -2,13 +2,16 @@
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).
> 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).
> [!warning] 18.x Drivers are not yet supported!
> Drivers are only supported until **17.x releases**.
This service can be used without internet connection.
Only the clients need a connection to this service on configured port.
@ -66,9 +69,6 @@ The images include database drivers for `postgres`, `mariadb` and `sqlite`.
WORKING_DIR=/opt/docker/fastapi-dls/cert
mkdir -p $WORKING_DIR
cd $WORKING_DIR
# create instance private and public key for singing JWT's
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
# create ssl certificate for integrated webserver (uvicorn) - because clients rely on ssl
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $WORKING_DIR/webserver.key -out $WORKING_DIR/webserver.crt
```
@ -153,9 +153,6 @@ chown -R www-data:www-data $WORKING_DIR
WORKING_DIR=/opt/fastapi-dls/app/cert
mkdir -p $WORKING_DIR
cd $WORKING_DIR
# create instance private and public key for singing JWT's
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
# create ssl certificate for integrated webserver (uvicorn) - because clients rely on ssl
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout $WORKING_DIR/webserver.key -out $WORKING_DIR/webserver.crt
chown -R www-data:www-data $WORKING_DIR
@ -255,9 +252,6 @@ CERT_DIR=${BASE_DIR}/app/cert
SERVICE_USER=dls
mkdir ${CERT_DIR}
cd ${CERT_DIR}
# create instance private and public key for singing JWT's
openssl genrsa -out ${CERT_DIR}/instance.private.pem 2048
openssl rsa -in ${CERT_DIR}/instance.private.pem -outform PEM -pubout -out ${CERT_DIR}/instance.public.pem
# create ssl certificate for integrated webserver (uvicorn) - because clients rely on ssl
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout ${CERT_DIR}/webserver.key -out ${CERT_DIR}/webserver.crt
chown -R ${SERVICE_USER} ${CERT_DIR}
@ -424,10 +418,11 @@ After first success you have to replace `--issue` with `--renew`.
# Configuration
| Variable | Default | Usage |
|--------------------------|----------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------|
|------------------------|----------------------------------------|------------------------------------------------------------------------------------------------------|
| `DEBUG` | `false` | Toggles `fastapi` debug mode |
| `DLS_URL` | `localhost` | Used in client-token to tell guest driver where dls instance is reachable |
| `DLS_PORT` | `443` | Used in client-token to tell guest driver where dls instance is reachable |
| `CERT_PATH` | `None` | Path to a Directory where generated Certificates are stored. Defaults to `/<app-dir>/cert`. |
| `TOKEN_EXPIRE_DAYS` | `1` | Client auth-token validity (used for authenticate client against api, **not `.tok` file!**) |
| `LEASE_EXPIRE_DAYS` | `90` | Lease time in days |
| `LEASE_RENEWAL_PERIOD` | `0.15` | The percentage of the lease period that must elapse before a licensed client can renew a license \*1 |
@ -436,8 +431,6 @@ After first success you have to replace `--issue` with `--renew`.
| `SITE_KEY_XID` | `00000000-0000-0000-0000-000000000000` | Site identification uuid |
| `INSTANCE_REF` | `10000000-0000-0000-0000-000000000001` | Instance identification uuid |
| `ALLOTMENT_REF` | `20000000-0000-0000-0000-000000000001` | Allotment identification uuid |
| `INSTANCE_KEY_RSA` | `<app-dir>/cert/instance.private.pem` | Site-wide private RSA key for singing JWTs \*3 |
| `INSTANCE_KEY_PUB` | `<app-dir>/cert/instance.public.pem` | Site-wide public key \*3 |
\*1 For example, if the lease period is one day and the renewal period is 20%, the client attempts to renew its license
every 4.8 hours. If network connectivity is lost, the loss of connectivity is detected during license renewal and the
@ -445,8 +438,6 @@ client has 19.2 hours in which to re-establish connectivity before its license e
\*2 Always use `https`, since guest-drivers only support secure connections!
\*3 If you recreate your instance keys you need to **recreate client-token for each guest**!
# Setup (Client)
**The token file has to be copied! It's not enough to C&P file contents, because there can be special characters.**
@ -545,6 +536,10 @@ Status endpoint, used for *healthcheck*.
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.
**`GET /-/readme`**
HTML rendered README.md.
@ -617,7 +612,7 @@ Please download a new client-token. The guest have to register within an hour af
### `jose.exceptions.JWTError: Signature verification failed.`
- Did you recreate `instance.public.pem` / `instance.private.pem`?
- Did you recreate any certificate or keypair?
Then you have to download a **new** client-token on each of your guests.
@ -753,33 +748,25 @@ The error message can safely be ignored (since we have no license limitation :P)
# vGPU Software Compatibility Matrix
**18.x Drivers are not supported on FastAPI-DLS Versions < 1.6.0**
<details>
<summary>Show Table</summary>
Successfully tested with this package versions.
| vGPU Suftware | Driver Branch | Linux vGPU Manager | Linux Driver | Windows Driver | Release Date | EOL Date |
|:-------------:|:-------------:|--------------------|--------------|----------------|--------------:|--------------:|
| `17.5` | R550 | `550.144.02` | `550.144.03` | `553.62` | January 2025 | June 2025 |
| `17.4` | R550 | `550.127.06` | `550.127.05` | `553.24` | October 2024 | |
| `17.3` | R550 | `550.90.05` | `550.90.07` | `552.74` | July 2024 | |
| `17.2` | R550 | `550.90.05` | `550.90.07` | `552.55` | June 2024 | |
| `17.1` | R550 | `550.54.16` | `550.54.15` | `551.78` | March 2024 | |
| `17.0` | R550 | `550.54.10` | `550.54.14` | `551.61` | February 2024 | |
| `16.9` | R535 | `535.230.02` | `535.216.01` | `539.19` | October 2024 | July 2026 |
| `16.8` | R535 | `535.216.01` | `535.216.01` | `538.95` | October 2024 | |
| `16.7` | R535 | `535.183.04` | `535.183.06` | `538.78` | July 2024 | |
| `16.6` | R535 | `535.183.04` | `535.183.01` | `538.67` | June 2024 | |
| `16.5` | R535 | `535.161.05` | `535.161.08` | `538.46` | February 2024 | |
| `16.4` | R535 | `535.161.05` | `535.161.07` | `538.33` | February 2024 | |
| `16.3` | R535 | `535.154.02` | `535.154.05` | `538.15` | January 2024 | |
| `16.2` | R535 | `535.129.03` | `535.129.03` | `537.70` | October 2023 | |
| `16.1` | R535 | `535.104.06` | `535.104.05` | `537.13` | August 2023 | |
| `16.0` | R535 | `535.54.06` | `535.54.03` | `536.22` | July 2023 | |
| `15.4` | R525 | `525.147.01` | `525.147.05` | `529.19` | June 2023 | December 2023 |
| `14.4` | R510 | `510.108.03` | `510.108.03` | `514.08` | December 2022 | February 2023 |
| 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 |
| `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 | |
| | `17.3` | | `550.90.05` | `550.90.07` | `552.74` | July 2024 | |
| | `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` | `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 |
</details>
@ -795,13 +782,14 @@ Thanks to vGPU community and all who uses this project and report bugs.
Special thanks to:
- @samicrusader who created build file for **ArchLinux**
- @cyrus who wrote the section for **openSUSE**
- @midi who wrote the section for **unRAID**
- @polloloco who wrote the *[NVIDIA vGPU Guide](https://gitlab.com/polloloco/vgpu-proxmox)*
- @DualCoder who creates the `vgpu_unlock` functionality [vgpu_unlock](https://github.com/DualCoder/vgpu_unlock)
- 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)
- `samicrusader` who created build file for **ArchLinux**
- `cyrus` who wrote the section for **openSUSE**
- `midi` who wrote the section for **unRAID**
- `polloloco` who wrote the *[NVIDIA vGPU Guide](https://gitlab.com/polloloco/vgpu-proxmox)*
- `DualCoder` who creates the `vgpu_unlock` functionality [vgpu_unlock](https://github.com/DualCoder/vgpu_unlock)
- `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)
And thanks to all people who contributed to all these libraries!

View File

@ -4,24 +4,25 @@ from calendar import timegm
from contextlib import asynccontextmanager
from datetime import datetime, timedelta, UTC
from hashlib import sha256
from json import loads as json_loads
from json import loads as json_loads, dumps as json_dumps
from os import getenv as env
from os.path import join, dirname
from textwrap import wrap
from uuid import uuid4
from dateutil.relativedelta import relativedelta
from dotenv import load_dotenv
from fastapi import FastAPI
from fastapi.requests import Request
from fastapi.responses import Response, RedirectResponse, StreamingResponse
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
from starlette.responses import StreamingResponse, JSONResponse as JSONr, HTMLResponse as HTMLr, Response, RedirectResponse
from orm import Origin, Lease, init as db_init, migrate
from util import PrivateKey, PublicKey, load_file
from util import CASetup, PrivateKey, Cert, ProductMapping, load_file
# Load variables
load_dotenv('../version.env')
@ -39,20 +40,31 @@ db_init(db), migrate(db)
# Load DLS variables (all prefixed with "INSTANCE_*" is used as "SERVICE_INSTANCE_*" or "SI_*" in official dls service)
DLS_URL = str(env('DLS_URL', 'localhost'))
DLS_PORT = int(env('DLS_PORT', '443'))
CERT_PATH = str(env('CERT_PATH', None))
SITE_KEY_XID = str(env('SITE_KEY_XID', '00000000-0000-0000-0000-000000000000'))
INSTANCE_REF = str(env('INSTANCE_REF', '10000000-0000-0000-0000-000000000001'))
ALLOTMENT_REF = str(env('ALLOTMENT_REF', '20000000-0000-0000-0000-000000000001'))
INSTANCE_KEY_RSA = PrivateKey.from_file(str(env('INSTANCE_KEY_RSA', join(dirname(__file__), 'cert/instance.private.pem'))))
INSTANCE_KEY_PUB = PublicKey.from_file(str(env('INSTANCE_KEY_PUB', join(dirname(__file__), 'cert/instance.public.pem'))))
TOKEN_EXPIRE_DELTA = relativedelta(days=int(env('TOKEN_EXPIRE_DAYS', 1)), hours=int(env('TOKEN_EXPIRE_HOURS', 0)))
LEASE_EXPIRE_DELTA = relativedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)), hours=int(env('LEASE_EXPIRE_HOURS', 0)))
LEASE_RENEWAL_PERIOD = float(env('LEASE_RENEWAL_PERIOD', 0.15))
LEASE_RENEWAL_DELTA = timedelta(days=int(env('LEASE_EXPIRE_DAYS', 90)), hours=int(env('LEASE_EXPIRE_HOURS', 0)))
CLIENT_TOKEN_EXPIRE_DELTA = relativedelta(years=12)
CORS_ORIGINS = str(env('CORS_ORIGINS', '')).split(',') if (env('CORS_ORIGINS')) else [f'https://{DLS_URL}']
DT_FORMAT = '%Y-%m-%dT%H:%M:%S.%fZ'
PRODUCT_MAPPING = ProductMapping(filename=join(dirname(__file__), 'static/product_mapping.json'))
jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.pem(), algorithm=ALGORITHMS.RS256)
jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.pem(), algorithm=ALGORITHMS.RS256)
# Create certificate chain and signing keys
ca_setup = CASetup(service_instance_ref=INSTANCE_REF, cert_path=CERT_PATH)
my_root_private_key = PrivateKey.from_file(ca_setup.root_private_key_filename)
my_root_public_key = my_root_private_key.public_key()
my_root_certificate = Cert.from_file(ca_setup.root_certificate_filename)
my_ca_certificate = Cert.from_file(ca_setup.ca_certificate_filename)
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 = 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
@ -119,12 +131,12 @@ async def _index():
@app.get('/-/health', summary='* Health')
async def _health():
return JSONr({'status': 'up'})
return Response(content=json_dumps({'status': 'up'}), media_type='application/json', status_code=200)
@app.get('/-/config', summary='* Config', description='returns environment variables.')
async def _config():
return JSONr({
response = {
'VERSION': str(VERSION),
'COMMIT': str(COMMIT),
'DEBUG': str(DEBUG),
@ -138,14 +150,22 @@ async def _config():
'LEASE_RENEWAL_PERIOD': str(LEASE_RENEWAL_PERIOD),
'CORS_ORIGINS': str(CORS_ORIGINS),
'TZ': str(TZ),
})
}
return Response(content=json_dumps(response), media_type='application/json', status_code=200)
@app.get('/-/config/root-certificate', summary='* Root Certificate', description='returns Root--Certificate needed for patching nvidia-gridd')
async def _config():
return Response(content=my_root_certificate.pem().decode('utf-8').strip(), media_type='text/plain')
@app.get('/-/readme', summary='* Readme')
async def _readme():
from markdown import markdown
content = load_file(join(dirname(__file__), '../README.md')).decode('utf-8')
return HTMLr(markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc']))
response = markdown(text=content, extensions=['tables', 'fenced_code', 'md_in_html', 'nl2br', 'toc'])
return Response(response, media_type='text/html', status_code=200)
@app.get('/-/manage', summary='* Management UI')
@ -183,7 +203,7 @@ async def _manage(request: Request):
</body>
</html>
'''
return HTMLr(response)
return Response(response, media_type='text/html', status_code=200)
@app.get('/-/origins', summary='* Origins')
@ -197,7 +217,7 @@ async def _origins(request: Request, leases: bool = False):
x['leases'] = list(map(lambda _: _.serialize(**serialize), Lease.find_by_origin_ref(db, origin.origin_ref)))
response.append(x)
session.close()
return JSONr(response)
return Response(content=json_dumps(response), media_type='application/json', status_code=200)
@app.delete('/-/origins', summary='* Origins')
@ -219,7 +239,7 @@ async def _leases(request: Request, origin: bool = False):
x['origin'] = lease_origin.serialize()
response.append(x)
session.close()
return JSONr(response)
return Response(content=json_dumps(response), media_type='application/json', status_code=200)
@app.delete('/-/leases/expired', summary='* Leases')
@ -232,7 +252,8 @@ async def _lease_delete_expired(request: Request):
async def _lease_delete(request: Request, lease_ref: str):
if Lease.delete(db, lease_ref) == 1:
return Response(status_code=201)
return JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'})
response = {'status': 404, 'detail': 'lease not found'}
return Response(content=json_dumps(response), media_type='application/json', status_code=404)
# venv/lib/python3.9/site-packages/nls_core_service_instance/service_instance_token_manager.py
@ -248,6 +269,7 @@ async def _client_token():
"iat": timegm(cur_time.timetuple()),
"nbf": timegm(cur_time.timetuple()),
"exp": timegm(exp_time.timetuple()),
"protocol_version": "2.0",
"update_mode": "ABSOLUTE",
"scope_ref_list": [ALLOTMENT_REF],
"fulfillment_class_ref_list": [],
@ -257,6 +279,7 @@ async def _client_token():
{
"idx": 0,
"d_name": "DLS",
# todo: {"service": "quick_release", "port": 80} - see "shutdown for windows"
"svc_port_map": [{"service": "auth", "port": DLS_PORT}, {"service": "lease", "port": DLS_PORT}]
}
],
@ -264,10 +287,10 @@ async def _client_token():
},
"service_instance_public_key_configuration": {
"service_instance_public_key_me": {
"mod": hex(INSTANCE_KEY_PUB.raw().public_numbers().n)[2:],
"exp": int(INSTANCE_KEY_PUB.raw().public_numbers().e),
"mod": my_si_public_key.mod(),
"exp": my_si_public_key.exp(),
},
"service_instance_public_key_pem": INSTANCE_KEY_PUB.pem().decode('utf-8'),
"service_instance_public_key_pem": my_si_public_key.pem().decode('utf-8').strip(),
"key_retention_mode": "LATEST_ONLY"
},
}
@ -298,17 +321,22 @@ async def auth_v1_origin(request: Request):
Origin.create_or_update(db, data)
environment = {
'raw_env': j.get('environment')
}
environment.update(j.get('environment'))
response = {
"origin_ref": origin_ref,
"environment": j.get('environment'),
"environment": environment,
"svc_port_set_list": None,
"node_url_list": None,
"node_query_order": None,
"prompts": None,
"sync_timestamp": cur_time.isoformat()
"sync_timestamp": cur_time.strftime(DT_FORMAT)
}
return JSONr(response)
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_origins_controller.py
@ -331,10 +359,10 @@ async def auth_v1_origin_update(request: Request):
response = {
"environment": j.get('environment'),
"prompts": None,
"sync_timestamp": cur_time.isoformat()
"sync_timestamp": cur_time.strftime(DT_FORMAT)
}
return JSONr(response)
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py
@ -362,11 +390,11 @@ async def auth_v1_code(request: Request):
response = {
"auth_code": auth_code,
"sync_timestamp": cur_time.isoformat(),
"prompts": None
"prompts": None,
"sync_timestamp": cur_time.strftime(DT_FORMAT),
}
return JSONr(response)
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
# venv/lib/python3.9/site-packages/nls_services_auth/test/test_auth_controller.py
@ -378,7 +406,8 @@ async def auth_v1_token(request: Request):
try:
payload = jwt.decode(token=j.get('auth_code'), key=jwt_decode_key, algorithms=ALGORITHMS.RS256)
except JWTError as e:
return JSONr(status_code=400, content={'status': 400, 'title': 'invalid token', 'detail': str(e)})
response = {'status': 400, 'title': 'invalid token', 'detail': str(e)}
return Response(content=json_dumps(response), media_type='application/json', status_code=400)
origin_ref = payload.get('origin_ref')
logger.info(f'> [ auth ]: {origin_ref}: {j}')
@ -386,7 +415,8 @@ async def auth_v1_token(request: Request):
# validate the code challenge
challenge = b64enc(sha256(j.get('code_verifier').encode('utf-8')).digest()).rstrip(b'=').decode('utf-8')
if payload.get('challenge') != challenge:
return JSONr(status_code=401, content={'status': 401, 'detail': 'expected challenge did not match verifier'})
response = {'status': 401, 'detail': 'expected challenge did not match verifier'}
return Response(content=json_dumps(response), media_type='application/json', status_code=401)
access_expires_on = cur_time + TOKEN_EXPIRE_DELTA
@ -396,20 +426,90 @@ async def auth_v1_token(request: Request):
'iss': 'https://cls.nvidia.org',
'aud': 'https://cls.nvidia.org',
'exp': timegm(access_expires_on.timetuple()),
'origin_ref': origin_ref,
'key_ref': SITE_KEY_XID,
'kid': SITE_KEY_XID,
'origin_ref': origin_ref,
}
auth_token = jwt.encode(new_payload, key=jwt_encode_key, headers={'kid': payload.get('kid')}, algorithm=ALGORITHMS.RS256)
response = {
"expires": access_expires_on.isoformat(),
"auth_token": auth_token,
"sync_timestamp": cur_time.isoformat(),
"expires": access_expires_on.strftime(DT_FORMAT),
"prompts": None,
"sync_timestamp": cur_time.strftime(DT_FORMAT),
}
return JSONr(response)
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
# NLS 3.4.0 - venv/lib/python3.12/site-packages/nls_services_lease/test/test_lease_single_controller.py
@app.post('/leasing/v1/config-token', description='request to get config token for lease operations')
async def leasing_v1_config_token(request: Request):
j, cur_time = json_loads((await request.body()).decode('utf-8')), datetime.now(UTC)
cur_time = datetime.now(UTC)
exp_time = cur_time + CLIENT_TOKEN_EXPIRE_DELTA
payload = {
"iss": "NLS Service Instance",
"aud": "NLS Licensed Client",
"iat": timegm(cur_time.timetuple()),
"nbf": timegm(cur_time.timetuple()),
"exp": timegm(exp_time.timetuple()),
"protocol_version": "2.0",
"d_name": "DLS",
"service_instance_ref": j.get('service_instance_ref'),
"service_instance_public_key_configuration": {
"service_instance_public_key_me": {
"mod": my_si_public_key.mod(),
"exp": my_si_public_key.exp(),
},
"service_instance_public_key_pem": my_si_public_key.pem().decode('utf-8').strip(),
"key_retention_mode": "LATEST_ONLY"
},
}
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()
# 76 chars per line on original response with "\r\n"
"""
response_ca_chain = my_ca_certificate.pem().decode('utf-8').strip()
response_ca_chain = response_ca_chain.replace('-----BEGIN CERTIFICATE-----', '')
response_ca_chain = response_ca_chain.replace('-----END CERTIFICATE-----', '')
response_ca_chain = response_ca_chain.replace('\n', '')
response_ca_chain = wrap(response_ca_chain, 76)
response_ca_chain = '\r\n'.join(response_ca_chain)
response_ca_chain = f'-----BEGIN CERTIFICATE-----\r\n{response_ca_chain}\r\n-----END CERTIFICATE-----'
"""
response_si_certificate = my_si_certificate.pem().decode('utf-8').strip()
# 76 chars per line on original response with "\r\n"
"""
response_si_certificate = my_si_certificate.pem().decode('utf-8').strip()
response_si_certificate = response_si_certificate.replace('-----BEGIN CERTIFICATE-----', '')
response_si_certificate = response_si_certificate.replace('-----END CERTIFICATE-----', '')
response_si_certificate = response_si_certificate.replace('\n', '')
response_si_certificate = wrap(response_si_certificate, 76)
response_si_certificate = '\r\n'.join(response_si_certificate)
"""
response = {
"certificateConfiguration": {
"caChain": [response_ca_chain],
"publicCert": response_si_certificate,
"publicKey": {
"exp": my_si_certificate.public_key().exp(),
"mod": [my_si_certificate.public_key().mod()],
},
},
"configToken": config_token,
}
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
@ -420,43 +520,67 @@ async def leasing_v1_lessor(request: Request):
try:
token = __get_token(request)
except JWTError:
return JSONr(status_code=401, content={'status': 401, 'detail': 'token is not valid'})
response = {'status': 401, 'detail': 'token is not valid'}
return Response(content=json_dumps(response), media_type='application/json', status_code=401)
origin_ref = token.get('origin_ref')
scope_ref_list = j.get('scope_ref_list')
lease_proposal_list = j.get('lease_proposal_list')
logger.info(f'> [ create ]: {origin_ref}: create leases for scope_ref_list {scope_ref_list}')
lease_result_list = []
for scope_ref in scope_ref_list:
# if scope_ref not in [ALLOTMENT_REF]:
# return JSONr(status_code=500, detail=f'no service instances found for scopes: ["{scope_ref}"]')
# response = {'status': 400, 'detail': f'service instances not found for scopes: ["{scope_ref}"]')}
# return Response(content=json_dumps(response), media_type='application/json', status_code=400)
pass
lease_result_list = []
for lease_proposal in lease_proposal_list:
lease_ref = str(uuid4())
expires = cur_time + LEASE_EXPIRE_DELTA
product_name = lease_proposal.get('product').get('name')
feature_name = PRODUCT_MAPPING.get_feature_name(product_name=product_name)
lease_result_list.append({
"ordinal": 0,
# https://docs.nvidia.com/license-system/latest/nvidia-license-system-user-guide/index.html
"error": None,
"lease": {
"ref": lease_ref,
"created": cur_time.isoformat(),
"expires": expires.isoformat(),
"created": cur_time.strftime(DT_FORMAT),
"expires": expires.strftime(DT_FORMAT), # todo: lease_proposal.get('duration') => "P0Y0M0DT12H0M0S
"feature_name": feature_name,
"lease_intent_id": None,
"license_type": "CONCURRENT_COUNTED_SINGLE",
"metadata": None,
"offline_lease": False, # todo
"product_name": product_name,
"recommended_lease_renewal": LEASE_RENEWAL_PERIOD,
"offline_lease": "true",
"license_type": "CONCURRENT_COUNTED_SINGLE"
}
"ref": lease_ref,
},
"ordinal": None,
})
data = Lease(origin_ref=origin_ref, lease_ref=lease_ref, lease_created=cur_time, lease_expires=expires)
Lease.create_or_update(db, data)
response = {
"client_challenge": j.get('client_challenge'),
"lease_result_list": lease_result_list,
"result_code": "SUCCESS",
"sync_timestamp": cur_time.isoformat(),
"prompts": None
"prompts": None,
"result_code": None,
"sync_timestamp": cur_time.strftime(DT_FORMAT),
}
return JSONr(response)
content = json_dumps(response, separators=(',', ':'))
content = f'{content}\n'.encode('ascii')
signature = my_si_private_key.generate_signature(content)
headers = {
'Content-Type': 'application/json',
'access-control-expose-headers': 'X-NLS-Signature',
'X-NLS-Signature': f'{signature.hex().encode()}'
}
return Response(content=content, media_type='application/json', headers=headers)
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
@ -472,39 +596,54 @@ async def leasing_v1_lessor_lease(request: Request):
response = {
"active_lease_list": active_lease_list,
"sync_timestamp": cur_time.isoformat(),
"prompts": None
"prompts": None,
"sync_timestamp": cur_time.strftime(DT_FORMAT),
}
return JSONr(response)
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py
# venv/lib/python3.9/site-packages/nls_core_lease/lease_single.py
@app.put('/leasing/v1/lease/{lease_ref}', description='renew a lease')
async def leasing_v1_lease_renew(request: Request, lease_ref: str):
token, cur_time = __get_token(request), datetime.now(UTC)
j, token, cur_time = json_loads((await request.body()).decode('utf-8')), __get_token(request), datetime.now(UTC)
origin_ref = token.get('origin_ref')
logger.info(f'> [ renew ]: {origin_ref}: renew {lease_ref}')
entity = Lease.find_by_origin_ref_and_lease_ref(db, origin_ref, lease_ref)
if entity is None:
return JSONr(status_code=404, content={'status': 404, 'detail': 'requested lease not available'})
response = {'status': 404, 'detail': 'requested lease not available'}
return Response(content=json_dumps(response), media_type='application/json', status_code=404)
expires = cur_time + LEASE_EXPIRE_DELTA
response = {
"client_challenge": j.get('client_challenge'),
"expires": expires.strftime('%Y-%m-%dT%H:%M:%S.%f'), # DT_FORMAT => "trailing 'Z' missing in this response
"feature_expired": False,
"lease_ref": lease_ref,
"expires": expires.isoformat(),
"recommended_lease_renewal": LEASE_RENEWAL_PERIOD,
"offline_lease": True,
"metadata": None,
"offline_lease": False, # todo
"prompts": None,
"sync_timestamp": cur_time.isoformat(),
"recommended_lease_renewal": LEASE_RENEWAL_PERIOD,
"sync_timestamp": cur_time.strftime(DT_FORMAT),
}
Lease.renew(db, entity, expires, cur_time)
return JSONr(response)
content = json_dumps(response, separators=(',', ':'))
content = f'{content}\n'.encode('ascii')
signature = my_si_private_key.generate_signature(content)
headers = {
'Content-Type': 'application/json',
'access-control-expose-headers': 'X-NLS-Signature',
'X-NLS-Signature': f'{signature.hex().encode()}'
}
return Response(content=content, media_type='application/json', headers=headers)
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py
@ -517,20 +656,24 @@ async def leasing_v1_lease_delete(request: Request, lease_ref: str):
entity = Lease.find_by_lease_ref(db, lease_ref)
if entity.origin_ref != origin_ref:
return JSONr(status_code=403, content={'status': 403, 'detail': 'access or operation forbidden'})
response = {'status': 403, 'detail': 'access or operation forbidden'}
return Response(content=json_dumps(response), media_type='application/json', status_code=403)
if entity is None:
return JSONr(status_code=404, content={'status': 404, 'detail': 'requested lease not available'})
response = {'status': 404, 'detail': 'requested lease not available'}
return Response(content=json_dumps(response), media_type='application/json', status_code=404)
if Lease.delete(db, lease_ref) == 0:
return JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'})
response = {'status': 404, 'detail': 'lease not found'}
return Response(content=json_dumps(response), media_type='application/json', status_code=404)
response = {
"client_challenge": None,
"lease_ref": lease_ref,
"prompts": None,
"sync_timestamp": cur_time.isoformat(),
"sync_timestamp": cur_time.strftime(DT_FORMAT),
}
return JSONr(response)
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
@ -547,11 +690,11 @@ async def leasing_v1_lessor_lease_remove(request: Request):
response = {
"released_lease_list": released_lease_list,
"release_failure_list": None,
"sync_timestamp": cur_time.isoformat(),
"prompts": None
"prompts": None,
"sync_timestamp": cur_time.strftime(DT_FORMAT),
}
return JSONr(response)
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
@app.post('/leasing/v1/lessor/shutdown', description='shutdown all leases')
@ -569,11 +712,11 @@ async def leasing_v1_lessor_shutdown(request: Request):
response = {
"released_lease_list": released_lease_list,
"release_failure_list": None,
"sync_timestamp": cur_time.isoformat(),
"prompts": None
"prompts": None,
"sync_timestamp": cur_time.strftime(DT_FORMAT),
}
return JSONr(response)
return Response(content=json_dumps(response, separators=(',', ':')), media_type='application/json', status_code=200)
if __name__ == '__main__':

View File

@ -5,7 +5,7 @@ from sqlalchemy import Column, VARCHAR, CHAR, ForeignKey, DATETIME, update, and_
from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker, declarative_base
from util import NV
from util import DriverMatrix
Base = declarative_base()
@ -25,7 +25,7 @@ class Origin(Base):
return f'Origin(origin_ref={self.origin_ref}, hostname={self.hostname})'
def serialize(self) -> dict:
_ = NV().find(self.guest_driver_version)
_ = DriverMatrix().find(self.guest_driver_version)
return {
'origin_ref': self.origin_ref,

View File

@ -0,0 +1,643 @@
{
"product": [
{
"xid": "c0ce7114-d8a5-40d4-b8b0-df204f4ff631",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVIDIA-vComputeServer-9.0",
"name": "NVIDIA-vComputeServer-9.0",
"description": null
},
{
"xid": "2a99638e-493f-424b-bc3a-629935307490",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "vGaming_Flexera_License-0.1",
"name": "vGaming_Flexera_License-0.1",
"description": null
},
{
"xid": "a013d60c-3cd6-4e61-ae51-018b5e342178",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID-Virtual-Apps-3.0",
"name": "GRID-Virtual-Apps-3.0",
"description": null
},
{
"xid": "bb99c6a3-81ce-4439-aef5-9648e75dd878",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID-vGaming-NLS-Metered-8.0",
"name": "GRID-vGaming-NLS-Metered-8.0",
"description": null
},
{
"xid": "c653e131-695c-4477-b77c-42ade3dcb02c",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID-Virtual-WS-Ext-2.0",
"name": "GRID-Virtual-WS-Ext-2.0",
"description": null
},
{
"xid": "6fc224ef-e0b5-467b-9bbb-d31c9eb7c6fc",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID-vGaming-8.0",
"name": "GRID-vGaming-8.0",
"description": null
},
{
"xid": "3c88888d-ebf3-4df7-9e86-c97d5b29b997",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID-Virtual-PC-2.0",
"name": "GRID-Virtual-PC-2.0",
"description": null
},
{
"xid": "66744b41-1fff-49be-a5a6-4cbd71b1117e",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVAIE_Licensing-1.0",
"name": "NVAIE_Licensing-1.0",
"description": null
},
{
"xid": "1d4e9ebc-a78c-41f4-a11a-de38a467b2ba",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVIDIA-vComputeServer NLS Metered-9.0",
"name": "NVIDIA-vComputeServer NLS Metered-9.0",
"description": null
},
{
"xid": "2152f8aa-d17b-46f5-8f5f-6f8c0760ce9c",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "vGaming_FB_License-0.1",
"name": "vGaming_FB_License-0.1",
"description": null
},
{
"xid": "54cbe0e8-7b35-4068-b058-e11f5b367c66",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "Quadro-Virtual-DWS-5.0",
"name": "Quadro-Virtual-DWS-5.0",
"description": null
},
{
"xid": "07a1d2b5-c147-48bc-bf44-9390339ca388",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID-Virtual-WS-2.0",
"name": "GRID-Virtual-WS-2.0",
"description": null
},
{
"xid": "82d7a5f0-0c26-11ef-b3b6-371045c70906",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "vGaming_Flexera_License-0.1",
"name": "vGaming_Flexera_License-0.1",
"description": null
},
{
"xid": "bdfbde00-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVIDIA Virtual Applications",
"name": "NVIDIA Virtual Applications",
"description": null
},
{
"xid": "bdfbe16d-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVIDIA Virtual PC",
"name": "NVIDIA Virtual PC",
"description": null
},
{
"xid": "bdfbe308-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVIDIA RTX Virtual Workstation",
"name": "NVIDIA RTX Virtual Workstation",
"description": null
},
{
"xid": "bdfbe405-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVIDIA vGaming",
"name": "NVIDIA vGaming",
"description": null
},
{
"xid": "bdfbe509-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID Virtual Applications",
"name": "GRID Virtual Applications",
"description": null
},
{
"xid": "bdfbe5c6-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID Virtual PC",
"name": "GRID Virtual PC",
"description": null
},
{
"xid": "bdfbe6e8-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "Quadro Virtual Data Center Workstation",
"name": "Quadro Virtual Data Center Workstation",
"description": null
},
{
"xid": "bdfbe7c8-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "GRID vGaming",
"name": "GRID vGaming",
"description": null
},
{
"xid": "bdfbe884-2cdb-11ec-9838-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVIDIA Virtual Compute Server",
"name": "NVIDIA Virtual Compute Server",
"description": null
},
{
"xid": "f09b5c33-5c07-11ed-9fa6-061a22468b59",
"product_family_xid": "bda4d909-2cdb-11ec-9838-061a22468b59",
"identifier": "NVIDIA OVE Licensing",
"name": "NVIDIA Omniverse Nucleus",
"description": null
}
],
"product_fulfillment": [
{
"xid": "cf0a5330-b583-4d9f-84bb-cfc8ce0917bb",
"product_xid": "07a1d2b5-c147-48bc-bf44-9390339ca388",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "90d0f05f-9431-4a15-86e7-740a4f08d457",
"product_xid": "1d4e9ebc-a78c-41f4-a11a-de38a467b2ba",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "327385dd-4ba8-4b3c-bc56-30bcf58ae9a3",
"product_xid": "2152f8aa-d17b-46f5-8f5f-6f8c0760ce9c",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "6733f2cc-0736-47ee-bcc8-20c4c624ce37",
"product_xid": "2a99638e-493f-424b-bc3a-629935307490",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "f35396a9-24f8-44b6-aa6a-493b335f4d56",
"product_xid": "3c88888d-ebf3-4df7-9e86-c97d5b29b997",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "6c7981d3-7192-4bfd-b7ec-ea2ad0b466dc",
"product_xid": "54cbe0e8-7b35-4068-b058-e11f5b367c66",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "9bd09610-6190-4684-9be6-3d9503833e80",
"product_xid": "66744b41-1fff-49be-a5a6-4cbd71b1117e",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "a4282e5b-ea08-4e0a-b724-7f4059ba99de",
"product_xid": "6fc224ef-e0b5-467b-9bbb-d31c9eb7c6fc",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "5cf793fc-1fb3-45c0-a711-d3112c775cbe",
"product_xid": "a013d60c-3cd6-4e61-ae51-018b5e342178",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "eb2d39a4-6370-4464-8a6a-ec3f42c69cb5",
"product_xid": "bb99c6a3-81ce-4439-aef5-9648e75dd878",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "e9df1c70-7fac-4c84-b54c-66e922b9791a",
"product_xid": "c0ce7114-d8a5-40d4-b8b0-df204f4ff631",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "6a4d5bcd-7b81-4e22-a289-ce3673e5cabf",
"product_xid": "c653e131-695c-4477-b77c-42ade3dcb02c",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "9e162d3c-0c26-11ef-b3b6-371045c70906",
"product_xid": "82d7a5f0-0c26-11ef-b3b6-371045c70906",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be2769b9-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbde00-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be276d7b-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbe16d-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be276efe-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbe308-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be276ff0-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbe405-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be2770af-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbe509-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be277164-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbe5c6-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be277214-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbe6e8-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be2772c8-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbe7c8-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "be277379-2cdb-11ec-9838-061a22468b59",
"product_xid": "bdfbe884-2cdb-11ec-9838-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
},
{
"xid": "c4284597-5c09-11ed-9fa6-061a22468b59",
"product_xid": "f09b5c33-5c07-11ed-9fa6-061a22468b59",
"qualifier_specification": null,
"evaluation_order_index": 0
}
],
"product_fulfillment_feature": [
{
"xid": "9ca32d2b-736e-4e4f-8f5a-895a755b4c41",
"product_fulfillment_xid": "5cf793fc-1fb3-45c0-a711-d3112c775cbe",
"feature_identifier": "GRID-Virtual-Apps",
"feature_version": "3.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "d8b25329-f47f-43dc-a278-f2d38f9e939b",
"product_fulfillment_xid": "f35396a9-24f8-44b6-aa6a-493b335f4d56",
"feature_identifier": "GRID-Virtual-PC",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "e7102df8-d88a-4bd0-aa79-9a53d8b77888",
"product_fulfillment_xid": "cf0a5330-b583-4d9f-84bb-cfc8ce0917bb",
"feature_identifier": "GRID-Virtual-WS",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "30761db3-0afe-454d-b284-efba6d9b13a3",
"product_fulfillment_xid": "6a4d5bcd-7b81-4e22-a289-ce3673e5cabf",
"feature_identifier": "GRID-Virtual-WS-Ext",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "10fd7701-83ae-4caf-a27f-75880fab23f6",
"product_fulfillment_xid": "a4282e5b-ea08-4e0a-b724-7f4059ba99de",
"feature_identifier": "GRID-vGaming",
"feature_version": "8.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "cbd61276-fb1e-42e1-b844-43e94465da8f",
"product_fulfillment_xid": "9bd09610-6190-4684-9be6-3d9503833e80",
"feature_identifier": "NVAIE_Licensing",
"feature_version": "1.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "6b1c74b5-1511-46ee-9f12-8bc6d5636fef",
"product_fulfillment_xid": "90d0f05f-9431-4a15-86e7-740a4f08d457",
"feature_identifier": "NVIDIA-vComputeServer NLS Metered",
"feature_version": "9.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "db53af09-7295-48b7-b927-24b23690c959",
"product_fulfillment_xid": "e9df1c70-7fac-4c84-b54c-66e922b9791a",
"feature_identifier": "NVIDIA-vComputeServer",
"feature_version": "9.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "1f62be61-a887-4e54-a34e-61cfa7b2db30",
"product_fulfillment_xid": "6c7981d3-7192-4bfd-b7ec-ea2ad0b466dc",
"feature_identifier": "Quadro-Virtual-DWS",
"feature_version": "5.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "8a4b5e98-f1ca-4c18-b0d4-8f4f9f0462e2",
"product_fulfillment_xid": "327385dd-4ba8-4b3c-bc56-30bcf58ae9a3",
"feature_identifier": "vGaming_FB_License",
"feature_version": "0.1",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be531e98-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be2769b9-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-Apps",
"feature_version": "3.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be53219e-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276d7b-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-PC",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be5322f0-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276d7b-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "Quadro-Virtual-DWS",
"feature_version": "5.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 1
},
{
"xid": "be5323d8-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276d7b-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-WS",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 2
},
{
"xid": "be5324a6-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276d7b-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-WS-Ext",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 3
},
{
"xid": "be532568-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276efe-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "Quadro-Virtual-DWS",
"feature_version": "5.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be532630-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276efe-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-WS",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 1
},
{
"xid": "be5326e7-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276efe-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-WS-Ext",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 2
},
{
"xid": "be5327a7-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276ff0-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-vGaming",
"feature_version": "8.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be532923-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be2770af-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-Apps",
"feature_version": "3.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be5329e0-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277164-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-PC",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be532aa0-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277164-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "Quadro-Virtual-DWS",
"feature_version": "5.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 1
},
{
"xid": "be532b5c-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277164-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-WS",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 2
},
{
"xid": "be532c19-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277164-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-WS-Ext",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 3
},
{
"xid": "be532ccb-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277214-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "Quadro-Virtual-DWS",
"feature_version": "5.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be532d92-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277214-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-WS",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 1
},
{
"xid": "be532e45-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277214-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-Virtual-WS-Ext",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 2
},
{
"xid": "be532efa-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be2772c8-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-vGaming",
"feature_version": "8.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be53306d-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277379-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "NVIDIA-vComputeServer",
"feature_version": "9.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "be533228-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277379-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "NVIDIA-vComputeServer NLS Metered",
"feature_version": "9.0",
"license_type_identifier": "CONCURRENT_UNCOUNTED_SINGLE",
"evaluation_order_index": 2
},
{
"xid": "be5332f6-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277379-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "NVAIE_Licensing",
"feature_version": "1.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 3
},
{
"xid": "15ff4f16-57a8-4593-93ec-58352a256f12",
"product_fulfillment_xid": "eb2d39a4-6370-4464-8a6a-ec3f42c69cb5",
"feature_identifier": "GRID-vGaming-NLS-Metered",
"feature_version": "8.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 3
},
{
"xid": "0c1552ca-3ef8-11ed-9fa6-061a22468b59",
"product_fulfillment_xid": "be276ff0-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "vGaming_Flexera_License",
"feature_version": "0.1",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 1
},
{
"xid": "31c3be8c-5c0a-11ed-9fa6-061a22468b59",
"product_fulfillment_xid": "c4284597-5c09-11ed-9fa6-061a22468b59",
"feature_identifier": "OVE_Licensing",
"feature_version": "1.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
},
{
"xid": "6caeb4cf-360f-11ee-b67d-02f279bf2bff",
"product_fulfillment_xid": "be277379-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "NVAIE_Licensing",
"feature_version": "2.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 4
},
{
"xid": "7fb1d01d-3f0e-11ed-9fa6-061a22468b59",
"product_fulfillment_xid": "be276ff0-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "vGaming_FB_License",
"feature_version": "0.1",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 2
},
{
"xid": "8eabcb08-3f0e-11ed-9fa6-061a22468b59",
"product_fulfillment_xid": "be2772c8-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "vGaming_FB_License",
"feature_version": "0.1",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 2
},
{
"xid": "a1dfe741-3e49-11ed-9fa6-061a22468b59",
"product_fulfillment_xid": "be2772c8-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "vGaming_Flexera_License",
"feature_version": "0.1",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 1
},
{
"xid": "be53286a-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be276ff0-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-vGaming-NLS-Metered",
"feature_version": "8.0",
"license_type_identifier": "CONCURRENT_UNCOUNTED_SINGLE",
"evaluation_order_index": 3
},
{
"xid": "be532fb2-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be2772c8-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "GRID-vGaming-NLS-Metered",
"feature_version": "8.0",
"license_type_identifier": "CONCURRENT_UNCOUNTED_SINGLE",
"evaluation_order_index": 3
},
{
"xid": "be533144-2cdb-11ec-9838-061a22468b59",
"product_fulfillment_xid": "be277379-2cdb-11ec-9838-061a22468b59",
"feature_identifier": "Quadro-Virtual-DWS",
"feature_version": "0.0",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 1
},
{
"xid": "bf105e18-0c26-11ef-b3b6-371045c70906",
"product_fulfillment_xid": "9e162d3c-0c26-11ef-b3b6-371045c70906",
"feature_identifier": "vGaming_Flexera_License",
"feature_version": "0.1",
"license_type_identifier": "CONCURRENT_COUNTED_SINGLE",
"evaluation_order_index": 0
}
]
}

View File

@ -1,12 +1,232 @@
import logging
from datetime import datetime, UTC, timedelta
from json import loads as json_loads
from os.path import join, dirname, isfile, isdir
from cryptography.hazmat.primitives import serialization
from cryptography import x509
from cryptography.hazmat._oid import NameOID
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15
from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey, RSAPublicKey, generate_private_key
from cryptography.hazmat.primitives.hashes import SHA256
from cryptography.hazmat.primitives.serialization import Encoding
from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key
from cryptography.x509 import load_pem_x509_certificate, Certificate
logging.basicConfig()
def load_file(filename: str) -> bytes:
log = logging.getLogger(f'{__name__}')
log.debug(f'Loading contents of file "{filename}')
with open(filename, 'rb') as file:
content = file.read()
return content
class CASetup:
###
#
# https://git.collinwebdesigns.de/nvidia/nls/-/blob/main/src/test/test_config_token.py
#
###
ROOT_PRIVATE_KEY_FILENAME = 'root_private_key.pem'
ROOT_CERTIFICATE_FILENAME = 'root_certificate.pem'
CA_PRIVATE_KEY_FILENAME = 'ca_private_key.pem'
CA_CERTIFICATE_FILENAME = 'ca_certificate.pem'
SI_PRIVATE_KEY_FILENAME = 'si_private_key.pem'
SI_CERTIFICATE_FILENAME = 'si_certificate.pem'
def __init__(self, service_instance_ref: str, cert_path: str = None):
cert_path_prefix = join(dirname(__file__), 'cert')
if cert_path is not None and len(cert_path) > 0 and isdir(cert_path):
cert_path_prefix = cert_path
self.service_instance_ref = service_instance_ref
self.root_private_key_filename = join(cert_path_prefix, CASetup.ROOT_PRIVATE_KEY_FILENAME)
self.root_certificate_filename = join(dirname(__file__), 'cert', CASetup.ROOT_CERTIFICATE_FILENAME)
self.ca_private_key_filename = join(dirname(__file__), 'cert', CASetup.CA_PRIVATE_KEY_FILENAME)
self.ca_certificate_filename = join(dirname(__file__), 'cert', CASetup.CA_CERTIFICATE_FILENAME)
self.si_private_key_filename = join(dirname(__file__), 'cert', CASetup.SI_PRIVATE_KEY_FILENAME)
self.si_certificate_filename = join(dirname(__file__), 'cert', CASetup.SI_CERTIFICATE_FILENAME)
if not (isfile(self.root_private_key_filename)
and isfile(self.root_certificate_filename)
and isfile(self.ca_private_key_filename)
and isfile(self.ca_certificate_filename)
and isfile(self.si_private_key_filename)
and isfile(self.si_certificate_filename)):
self.init_config_token_demo()
def init_config_token_demo(self):
""" Create Root Key and Certificate """
# create root keypair
my_root_private_key = generate_private_key(public_exponent=65537, key_size=4096)
my_root_public_key = my_root_private_key.public_key()
# create root-certificate subject
my_root_subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'California'),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Nvidia'),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'Nvidia Licensing Service (NLS)'),
x509.NameAttribute(NameOID.COMMON_NAME, u'NLS Root CA'),
])
# create self-signed root-certificate
my_root_certificate = (
x509.CertificateBuilder()
.subject_name(my_root_subject)
.issuer_name(my_root_subject)
.public_key(my_root_public_key)
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(tz=UTC) - timedelta(days=1))
.not_valid_after(datetime.now(tz=UTC) + timedelta(days=365 * 10))
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
.add_extension(x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
key_cert_sign=True,
key_agreement=False,
content_commitment=False,
data_encipherment=False,
crl_sign=True,
encipher_only=False,
decipher_only=False),
critical=True
)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(my_root_public_key), critical=False)
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(my_root_public_key), critical=False)
.sign(my_root_private_key, hashes.SHA256()))
my_root_private_key_as_pem = my_root_private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
with open(self.root_private_key_filename, 'wb') as f:
f.write(my_root_private_key_as_pem)
with open(self.root_certificate_filename, 'wb') as f:
f.write(my_root_certificate.public_bytes(encoding=Encoding.PEM))
""" Create CA (Intermediate) Key and Certificate """
# create ca keypair
my_ca_private_key = generate_private_key(public_exponent=65537, key_size=4096)
my_ca_public_key = my_ca_private_key.public_key()
# create ca-certificate subject
my_ca_subject = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'California'),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Nvidia'),
x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'Nvidia Licensing Service (NLS)'),
x509.NameAttribute(NameOID.COMMON_NAME, u'NLS Intermediate CA'),
])
# create self-signed ca-certificate
my_ca_certificate = (
x509.CertificateBuilder()
.subject_name(my_ca_subject)
.issuer_name(my_root_subject)
.public_key(my_ca_public_key)
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(tz=UTC) - timedelta(days=1))
.not_valid_after(datetime.now(tz=UTC) + timedelta(days=365 * 10))
.add_extension(x509.BasicConstraints(ca=True, path_length=None), critical=True)
.add_extension(x509.KeyUsage(
digital_signature=False,
key_encipherment=False,
key_cert_sign=True,
key_agreement=False,
content_commitment=False,
data_encipherment=False,
crl_sign=True,
encipher_only=False,
decipher_only=False),
critical=True
)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(my_ca_public_key), critical=False)
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
my_root_certificate.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value
), critical=False)
.sign(my_root_private_key, hashes.SHA256()))
my_ca_private_key_as_pem = my_ca_private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
with open(self.ca_private_key_filename, 'wb') as f:
f.write(my_ca_private_key_as_pem)
with open(self.ca_certificate_filename, 'wb') as f:
f.write(my_ca_certificate.public_bytes(encoding=Encoding.PEM))
""" Create Service-Instance Key and Certificate """
# create si keypair
my_si_private_key = generate_private_key(public_exponent=65537, key_size=2048)
my_si_public_key = my_si_private_key.public_key()
my_si_private_key_as_pem = my_si_private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
my_si_public_key_as_pem = my_si_public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
with open(self.si_private_key_filename, 'wb') as f:
f.write(my_si_private_key_as_pem)
# with open(self.si_public_key_filename, 'wb') as f:
# f.write(my_si_public_key_as_pem)
# create si-certificate subject
my_si_subject = x509.Name([
# x509.NameAttribute(NameOID.COMMON_NAME, INSTANCE_REF),
x509.NameAttribute(NameOID.COMMON_NAME, self.service_instance_ref),
])
# create self-signed si-certificate
my_si_certificate = (
x509.CertificateBuilder()
.subject_name(my_si_subject)
.issuer_name(my_ca_subject)
.public_key(my_si_public_key)
.serial_number(x509.random_serial_number())
.not_valid_before(datetime.now(tz=UTC) - timedelta(days=1))
.not_valid_after(datetime.now(tz=UTC) + timedelta(days=365 * 10))
.add_extension(x509.KeyUsage(digital_signature=True, key_encipherment=True, key_cert_sign=False,
key_agreement=True, content_commitment=False, data_encipherment=False,
crl_sign=False, encipher_only=False, decipher_only=False), critical=True)
.add_extension(x509.ExtendedKeyUsage([
x509.oid.ExtendedKeyUsageOID.SERVER_AUTH,
x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH]
), critical=False)
.add_extension(x509.SubjectKeyIdentifier.from_public_key(my_si_public_key), critical=False)
# .add_extension(x509.AuthorityKeyIdentifier.from_issuer_public_key(my_ca_public_key), critical=False)
.add_extension(x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(
my_ca_certificate.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value
), critical=False)
.add_extension(x509.SubjectAlternativeName([
# x509.DNSName(INSTANCE_REF)
x509.DNSName(self.service_instance_ref)
]), critical=False)
.sign(my_ca_private_key, hashes.SHA256()))
with open(self.si_certificate_filename, 'wb') as f:
f.write(my_si_certificate.public_bytes(encoding=Encoding.PEM))
class PrivateKey:
def __init__(self, data: bytes):
@ -39,6 +259,9 @@ class PrivateKey:
)
return PublicKey(data=data)
def generate_signature(self, data: bytes) -> bytes:
return self.__key.sign(data=data, padding=PKCS1v15(), algorithm=SHA256())
@staticmethod
def generate(public_exponent: int = 65537, key_size: int = 2048) -> "PrivateKey":
log = logging.getLogger(__name__)
@ -76,37 +299,78 @@ class PublicKey:
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
def load_file(filename: str) -> bytes:
log = logging.getLogger(f'{__name__}')
log.debug(f'Loading contents of file "{filename}')
with open(filename, 'rb') as file:
content = file.read()
return content
def mod(self) -> str:
return hex(self.__key.public_numbers().n)[2:]
def exp(self):
return int(self.__key.public_numbers().e)
def verify_signature(self, signature: bytes, data: bytes) -> None:
self.__key.verify(signature=signature, data=data, padding=PKCS1v15(), algorithm=SHA256())
class NV:
class Cert:
def __init__(self, data: bytes):
self.__cert = load_pem_x509_certificate(data)
@staticmethod
def from_file(filename: str) -> "Cert":
log = logging.getLogger(__name__)
log.debug(f'Importing Certificate from "{filename}"')
with open(filename, 'rb') as f:
data = f.read()
return Cert(data=data.strip())
def raw(self) -> Certificate:
return self.__cert
def pem(self) -> bytes:
return self.__cert.public_bytes(encoding=serialization.Encoding.PEM)
def public_key(self) -> "PublicKey":
data = self.__cert.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
return PublicKey(data=data)
def signature(self) -> bytes:
return self.__cert.signature
def subject_key_identifier(self):
return self.__cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value.key_identifier
def authority_key_identifier(self):
return self.__cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier).value.key_identifier
class DriverMatrix:
__DRIVER_MATRIX_FILENAME = 'static/driver_matrix.json'
__DRIVER_MATRIX: None | dict = None # https://docs.nvidia.com/grid/ => "Driver Versions"
def __init__(self):
self.log = logging.getLogger(self.__class__.__name__)
if NV.__DRIVER_MATRIX is None:
from json import load as json_load
if DriverMatrix.__DRIVER_MATRIX is None:
self.__load()
def __load(self):
try:
file = open(NV.__DRIVER_MATRIX_FILENAME)
NV.__DRIVER_MATRIX = json_load(file)
file.close()
self.log.debug(f'Successfully loaded "{NV.__DRIVER_MATRIX_FILENAME}".')
with open(DriverMatrix.__DRIVER_MATRIX_FILENAME, 'r') as f:
DriverMatrix.__DRIVER_MATRIX = json_loads(f.read())
self.log.debug(f'Successfully loaded "{DriverMatrix.__DRIVER_MATRIX_FILENAME}".')
except Exception as e:
NV.__DRIVER_MATRIX = {} # init empty dict to not try open file everytime, just when restarting app
DriverMatrix.__DRIVER_MATRIX = {} # init empty dict to not try open file everytime, just when restarting app
# self.log.warning(f'Failed to load "{NV.__DRIVER_MATRIX_FILENAME}": {e}')
@staticmethod
def find(version: str) -> dict | None:
if NV.__DRIVER_MATRIX is None:
if DriverMatrix.__DRIVER_MATRIX is None:
return None
for idx, (key, branch) in enumerate(NV.__DRIVER_MATRIX.items()):
for idx, (key, branch) in enumerate(DriverMatrix.__DRIVER_MATRIX.items()):
for release in branch.get('$releases'):
linux_driver = release.get('Linux Driver')
windows_driver = release.get('Windows Driver')
@ -126,3 +390,34 @@ class NV:
'is_latest': is_latest,
}
return None
class ProductMapping:
def __init__(self, filename: str):
with open(filename, 'r') as file:
self.data = json_loads(file.read())
def get_feature_name(self, product_name: str) -> (str, str):
product = self.__get_product(product_name)
product_fulfillment = self.__get_product_fulfillment(product.get('xid'))
feature = self.__get_product_fulfillment_feature(product_fulfillment.get('xid'))
return feature.get('feature_identifier')
def __get_product(self, product_name: str):
product_list = self.data.get('product')
return next(filter(lambda _: _.get('identifier') == product_name, product_list))
def __get_product_fulfillment(self, product_xid: str):
product_fulfillment_list = self.data.get('product_fulfillment')
return next(filter(lambda _: _.get('product_xid') == product_xid, product_fulfillment_list))
def __get_product_fulfillment_feature(self, product_fulfillment_xid: str):
feature_list = self.data.get('product_fulfillment_feature')
features = list(filter(lambda _: _.get('product_fulfillment_xid') == product_fulfillment_xid, feature_list))
features.sort(key=lambda _: _.get('evaluation_order_index'))
return features[0]

View File

@ -15,7 +15,7 @@ services:
<<: *dls-variables
volumes:
- /etc/timezone:/etc/timezone:ro
- /opt/docker/fastapi-dls/cert:/app/cert # instance.private.pem, instance.public.pem
- /opt/docker/fastapi-dls/cert:/app/cert
- db:/app/database
entrypoint: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "/app", "--proxy-headers"]
healthcheck:

View File

@ -1,8 +1,8 @@
fastapi==0.115.12
uvicorn[standard]==0.34.0
uvicorn[standard]==0.34.1
python-jose[cryptography]==3.4.0
cryptography==44.0.2
python-dateutil==2.9.0
sqlalchemy==2.0.40
markdown==3.7
markdown==3.8
python-dotenv==1.1.0

View File

@ -6,7 +6,7 @@ logger.setLevel(logging.INFO)
URL = 'https://docs.nvidia.com/vgpu/index.html'
BRANCH_STATUS_KEY, SOFTWARE_BRANCH_KEY, = 'vGPU Branch Status', 'vGPU Software Branch'
BRANCH_STATUS_KEY = 'vGPU Branch Status'
VGPU_KEY, GRID_KEY, DRIVER_BRANCH_KEY = 'vGPU Software', 'vGPU Software', 'Driver Branch'
LINUX_VGPU_MANAGER_KEY, LINUX_DRIVER_KEY = 'Linux vGPU Manager', 'Linux Driver'
WINDOWS_VGPU_MANAGER_KEY, WINDOWS_DRIVER_KEY = 'Windows vGPU Manager', 'Windows Driver'
@ -26,12 +26,15 @@ def __driver_versions(html: 'BeautifulSoup'):
# find wrapper for "DriverVersions" and find tables
data = html.find('div', {'id': 'driver-versions'})
items = data.findAll('bsp-accordion', {'class': 'Accordion-items-item'})
items = data.find_all('bsp-accordion', {'class': 'Accordion-items-item'})
for item in items:
software_branch = item.find('div', {'class': 'Accordion-items-item-title'}).text.strip()
software_branch = software_branch.replace(' Releases', '')
matrix_key = software_branch.lower()
branch_status = item.find('a', href=True, string='Branch status')
branch_status = branch_status.next_sibling.replace(':', '').strip()
# driver version info from table-heads (ths) and table-rows (trs)
table = item.find('table')
ths, trs = table.find_all('th'), table.find_all('tr')
@ -42,48 +45,20 @@ def __driver_versions(html: 'BeautifulSoup'):
continue
# create dict with table-heads as key and cell content as value
x = {headers[i]: __strip(cell.text) for i, cell in enumerate(tds)}
x.setdefault(BRANCH_STATUS_KEY, branch_status)
releases.append(x)
# add to matrix
MATRIX.update({matrix_key: {JSON_RELEASES_KEY: releases}})
def __release_branches(html: 'BeautifulSoup'):
# find wrapper for "AllReleaseBranches" and find table
data = html.find('div', {'id': 'all-release-branches'})
table = data.find('table')
# branch releases info from table-heads (ths) and table-rows (trs)
ths, trs = table.find_all('th'), table.find_all('tr')
headers = [header.text.strip() for header in ths]
for trs in trs:
tds = trs.find_all('td')
if len(tds) == 0: # skip empty
continue
# create dict with table-heads as key and cell content as value
x = {headers[i]: cell.text.strip() for i, cell in enumerate(tds)}
# get matrix_key
software_branch = x.get(SOFTWARE_BRANCH_KEY)
matrix_key = software_branch.lower()
# add to matrix
MATRIX.update({matrix_key: MATRIX.get(matrix_key) | x})
def __debug():
# print table head
s = f'{SOFTWARE_BRANCH_KEY:^21} | {BRANCH_STATUS_KEY:^21} | {VGPU_KEY:^13} | {LINUX_VGPU_MANAGER_KEY:^21} | {LINUX_DRIVER_KEY:^21} | {WINDOWS_VGPU_MANAGER_KEY:^21} | {WINDOWS_DRIVER_KEY:^21} | {RELEASE_DATE_KEY:>21} | {EOL_KEY:>21}'
s = f'{VGPU_KEY:^13} | {LINUX_VGPU_MANAGER_KEY:^21} | {LINUX_DRIVER_KEY:^21} | {WINDOWS_VGPU_MANAGER_KEY:^21} | {WINDOWS_DRIVER_KEY:^21} | {RELEASE_DATE_KEY:>21} | {BRANCH_STATUS_KEY:^21}'
print(s)
# iterate over dict & format some variables to not overload table
for idx, (key, branch) in enumerate(MATRIX.items()):
branch_status = branch.get(BRANCH_STATUS_KEY)
branch_status = branch_status.replace('Branch ', '')
branch_status = branch_status.replace('Long-Term Support', 'LTS')
branch_status = branch_status.replace('Production', 'Prod.')
software_branch = branch.get(SOFTWARE_BRANCH_KEY).replace('NVIDIA ', '')
for release in branch.get(JSON_RELEASES_KEY):
version = release.get(VGPU_KEY, release.get(GRID_KEY, ''))
linux_manager = release.get(LINUX_VGPU_MANAGER_KEY, release.get(ALT_VGPU_MANAGER_KEY, ''))
@ -92,13 +67,25 @@ def __debug():
windows_driver = release.get(WINDOWS_DRIVER_KEY)
release_date = release.get(RELEASE_DATE_KEY)
is_latest = release.get(VGPU_KEY) == branch.get(LATEST_KEY)
branch_status = __parse_branch_status(release.get(BRANCH_STATUS_KEY, ''))
version = f'{version} *' if is_latest else version
eol = branch.get(EOL_KEY) if is_latest else ''
s = f'{software_branch:^21} | {branch_status:^21} | {version:<13} | {linux_manager:<21} | {linux_driver:<21} | {windows_manager:<21} | {windows_driver:<21} | {release_date:>21} | {eol:>21}'
s = f'{version:<13} | {linux_manager:<21} | {linux_driver:<21} | {windows_manager:<21} | {windows_driver:<21} | {release_date:>21} | {branch_status:^21}'
print(s)
def __parse_branch_status(string: str) -> str:
string = string.replace('Production Branch', 'Prod. -')
string = string.replace('Long-Term Support Branch', 'LTS -')
string = string.replace('supported until', '')
string = string.replace('EOL since', 'EOL - ')
string = string.replace('EOL from', 'EOL -')
return string
def __dump(filename: str):
import json
@ -128,7 +115,6 @@ if __name__ == '__main__':
# build matrix
__driver_versions(soup)
__release_branches(soup)
# debug output
__debug()

View File

@ -1,13 +1,15 @@
import json
import sys
from base64 import b64encode as b64enc
from calendar import timegm
from datetime import datetime, UTC
from hashlib import sha256
from os.path import dirname, join
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
from jose import jwt, jwk
from jose import jwt, jwk, jws
from jose.constants import ALGORITHMS
from starlette.testclient import TestClient
@ -16,20 +18,28 @@ sys.path.append('../')
sys.path.append('../app')
from app import main
from util import PrivateKey, PublicKey
from util import CASetup, PrivateKey, PublicKey, Cert
client = TestClient(main.app)
# Instance
INSTANCE_REF = '10000000-0000-0000-0000-000000000001'
ORIGIN_REF, ALLOTMENT_REF, SECRET = str(uuid4()), '20000000-0000-0000-0000-000000000001', 'HelloWorld'
# INSTANCE_KEY_RSA = generate_key()
# INSTANCE_KEY_PUB = INSTANCE_KEY_RSA.public_key()
# CA & Signing
ca_setup = CASetup(service_instance_ref=INSTANCE_REF)
my_root_private_key = PrivateKey.from_file(ca_setup.root_private_key_filename)
my_root_certificate = Cert.from_file(ca_setup.root_certificate_filename)
my_ca_certificate = Cert.from_file(ca_setup.ca_certificate_filename)
my_ca_private_key = PrivateKey.from_file(ca_setup.ca_private_key_filename)
my_si_private_key = PrivateKey.from_file(ca_setup.si_private_key_filename)
my_si_private_key_as_pem = my_si_private_key.pem()
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)
INSTANCE_KEY_RSA = PrivateKey.from_file(str(join(dirname(__file__), '../app/cert/instance.private.pem')))
INSTANCE_KEY_PUB = PublicKey.from_file(str(join(dirname(__file__), '../app/cert/instance.public.pem')))
jwt_encode_key = jwk.construct(INSTANCE_KEY_RSA.pem(), algorithm=ALGORITHMS.RS256)
jwt_decode_key = jwk.construct(INSTANCE_KEY_PUB.pem(), algorithm=ALGORITHMS.RS256)
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:
@ -38,6 +48,48 @@ def __bearer_token(origin_ref: str) -> str:
return token
def test_signing():
signature_set_header = my_si_private_key.generate_signature(b'Hello')
# test plain
my_si_public_key.verify_signature(signature_set_header, b'Hello')
# test "X-NLS-Signature: b'....'
x_nls_signature_header_value = f'{signature_set_header.hex().encode()}'
assert f'{x_nls_signature_header_value}'.startswith('b\'')
assert f'{x_nls_signature_header_value}'.endswith('\'')
# test eval
signature_get_header = eval(x_nls_signature_header_value)
signature_get_header = bytes.fromhex(signature_get_header.decode('ascii'))
my_si_public_key.verify_signature(signature_get_header, b'Hello')
def test_keypair_and_certificates():
assert my_root_certificate.public_key().mod() == my_root_private_key.public_key().mod()
assert my_ca_certificate.public_key().mod() == my_ca_private_key.public_key().mod()
assert my_si_certificate.public_key().mod() == my_si_public_key.mod()
assert len(my_root_certificate.public_key().mod()) == 1024
assert len(my_ca_certificate.public_key().mod()) == 1024
assert len(my_si_certificate.public_key().mod()) == 512
#assert my_si_certificate.public_key().mod() != my_si_public_key.mod()
my_root_certificate.public_key().raw().verify(
my_ca_certificate.raw().signature,
my_ca_certificate.raw().tbs_certificate_bytes,
PKCS1v15(),
SHA256(),
)
my_ca_certificate.public_key().raw().verify(
my_si_certificate.raw().signature,
my_si_certificate.raw().tbs_certificate_bytes,
PKCS1v15(),
SHA256(),
)
def test_index():
response = client.get('/')
assert response.status_code == 200
@ -54,6 +106,12 @@ def test_config():
assert response.status_code == 200
def test_config_root_ca():
response = client.get('/-/config/root-certificate')
assert response.status_code == 200
assert response.content.decode('utf-8').strip() == my_root_certificate.pem().decode('utf-8').strip()
def test_readme():
response = client.get('/-/readme')
assert response.status_code == 200
@ -69,6 +127,41 @@ def test_client_token():
assert response.status_code == 200
def test_config_token():
# https://git.collinwebdesigns.de/nvidia/nls/-/blob/main/src/test/test_config_token.py
response = client.post('/leasing/v1/config-token', json={"service_instance_ref": INSTANCE_REF})
assert response.status_code == 200
nv_response_certificate_configuration = response.json().get('certificateConfiguration')
nv_ca_chain = nv_response_certificate_configuration.get('caChain')[0].encode('utf-8')
nv_ca_chain = Cert(nv_ca_chain)
nv_response_public_cert = nv_response_certificate_configuration.get('publicCert').encode('utf-8')
nv_response_public_key = nv_response_certificate_configuration.get('publicKey')
nv_si_certificate = Cert(nv_response_public_cert)
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_response_config_token = response.json().get('configToken')
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
nv_si_public_key_configuration = payload.get('service_instance_public_key_configuration')
nv_si_public_key_me = nv_si_public_key_configuration.get('service_instance_public_key_me')
assert len(nv_si_public_key_me.get('mod')) == 512 # nv_si_public_key_mod
assert nv_si_public_key_me.get('exp') == 65537 # nv_si_public_key_exp
def test_origins():
pass
@ -168,12 +261,13 @@ def test_auth_v1_token():
def test_leasing_v1_lessor():
payload = {
'client_challenge': 'my_unique_string',
'fulfillment_context': {
'fulfillment_class_ref_list': []
},
'lease_proposal_list': [{
'license_type_qualifiers': {'count': 1},
'product': {'name': 'NVIDIA RTX Virtual Workstation'}
'product': {'name': 'NVIDIA Virtual Applications'}
}],
'proposal_evaluation_mode': 'ALL_OF',
'scope_ref_list': [ALLOTMENT_REF]
@ -182,10 +276,21 @@ def test_leasing_v1_lessor():
response = client.post('/leasing/v1/lessor', json=payload, headers={'authorization': __bearer_token(ORIGIN_REF)})
assert response.status_code == 200
client_challenge = response.json().get('client_challenge')
assert client_challenge == payload.get('client_challenge')
signature = eval(response.headers.get('X-NLS-Signature'))
assert len(signature) == 512
signature = bytes.fromhex(signature.decode('ascii'))
assert len(signature) == 256
my_si_public_key.verify_signature(signature, response.content)
lease_result_list = response.json().get('lease_result_list')
assert len(lease_result_list) == 1
assert len(lease_result_list[0]['lease']['ref']) == 36
assert str(UUID(lease_result_list[0]['lease']['ref'])) == lease_result_list[0]['lease']['ref']
assert lease_result_list[0]['lease']['product_name'] == 'NVIDIA Virtual Applications'
assert lease_result_list[0]['lease']['feature_name'] == 'GRID-Virtual-Apps'
def test_leasing_v1_lessor_lease():
@ -205,9 +310,18 @@ def test_leasing_v1_lease_renew():
###
response = client.put(f'/leasing/v1/lease/{active_lease_ref}', headers={'authorization': __bearer_token(ORIGIN_REF)})
payload = {'client_challenge': 'my_unique_string'}
response = client.put(f'/leasing/v1/lease/{active_lease_ref}', json=payload, headers={'authorization': __bearer_token(ORIGIN_REF)})
assert response.status_code == 200
client_challenge = response.json().get('client_challenge')
assert client_challenge == payload.get('client_challenge')
signature = eval(response.headers.get('X-NLS-Signature'))
assert len(signature) == 512
signature = bytes.fromhex(signature.decode('ascii'))
assert len(signature) == 256
my_si_public_key.verify_signature(signature, response.content)
lease_ref = response.json().get('lease_ref')
assert len(lease_ref) == 36
assert lease_ref == active_lease_ref
@ -236,7 +350,7 @@ def test_leasing_v1_lessor_lease_remove():
},
'lease_proposal_list': [{
'license_type_qualifiers': {'count': 1},
'product': {'name': 'NVIDIA RTX Virtual Workstation'}
'product': {'name': 'NVIDIA Virtual Applications'}
}],
'proposal_evaluation_mode': 'ALL_OF',
'scope_ref_list': [ALLOTMENT_REF]