Compare commits

...

31 Commits

Author SHA1 Message Date
Oscar Krause
445a303955 added signature tests 2025-04-14 21:38:20 +02:00
Oscar Krause
79f1015a86 code styling 2025-04-14 21:38:20 +02:00
Oscar Krause
7b2d61b329 fixed signature 2025-04-14 20:29:50 +02:00
Oscar Krause
f9fccd5502 implemented product_mapping support 2025-04-14 20:29:50 +02:00
Oscar Krause
70cac5bbf3 test "X-NLS-Signature" upper/lowercase 2025-04-14 20:29:50 +02:00
Oscar Krause
fbb73c73ab changed "feature_name" 2025-04-14 20:29:50 +02:00
Oscar Krause
94a7772c7b updated attributes to match nvidia-nls response order 2025-04-14 20:29:50 +02:00
Oscar Krause
54d38953bf x-nls-signature 2025-04-14 20:29:50 +02:00
Oscar Krause
3871dfe6a6 test "x-nls-signature" 2025-04-14 20:29:50 +02:00
Oscar Krause
e69e93bcb4 fixed missing code 2025-04-14 20:29:50 +02:00
Oscar Krause
d7c29f834c test with "X-NLS-Signature" 2025-04-14 20:29:50 +02:00
Oscar Krause
685e1ce0bb implemented client_challenge on lease apis 2025-04-14 20:29:50 +02:00
Oscar Krause
d8cddd4b29 set "offline_lease" to false 2025-04-14 20:29:50 +02:00
Oscar Krause
60f7e40ca4 added EMPTY (!!!) X-NLS-Signature response header 2025-04-14 20:29:50 +02:00
Oscar Krause
672ddb16c7 added debug headers 2025-04-14 20:29:50 +02:00
Oscar Krause
beebf9e812 code styling 2025-04-14 20:29:50 +02:00
Oscar Krause
743c702921 added new "protocol_version" to client-token 2025-04-14 20:29:50 +02:00
Oscar Krause
601d733add fixes 2025-04-14 20:29:50 +02:00
Oscar Krause
2ffee4aee1 also debug response 2025-04-14 20:29:50 +02:00
Oscar Krause
5eb0c55f3f fixed datetime format 2025-04-14 20:29:50 +02:00
Oscar Krause
c7e8414934 added missing lessor attributes 2025-04-14 20:29:50 +02:00
Oscar Krause
30ef56f956 fixes 2025-04-14 20:29:50 +02:00
Oscar Krause
1908b44bed added debugging 2025-04-14 20:29:50 +02:00
Oscar Krause
0982106b4f updated new responses for 18.x drivers 2025-04-14 20:29:50 +02:00
Oscar Krause
c15cdee610 created "init_config_token_demo" 2025-04-14 20:29:50 +02:00
Oscar Krause
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
Oscar Krause
88fbd08610 implemented initial endpoint for /leasing/v1/config-token 2025-04-14 20:29:50 +02:00
Oscar Krause
e438de6281 added logging 2025-04-14 20:29:50 +02:00
Oscar Krause
d71b66d192 added debugging 2025-04-14 20:29:50 +02:00
Oscar Krause
61c9e47237 added endpoint '/leasing/v1/config-token' 2025-04-14 20:29:50 +02:00
Oscar Krause
9e5b4f5a42 Datei .gitlab-ci.yml aktualisieren 2025-04-14 20:13:02 +02:00
5 changed files with 1092 additions and 42 deletions

View File

@ -89,6 +89,7 @@ build:pacman:
interruptible: true
stage: build
rules:
- if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- if: $CI_COMMIT_TAG
variables:
VERSION: $CI_COMMIT_REF_NAME

View File

@ -4,24 +4,24 @@ 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 os.path import join, dirname, isfile
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 StreamingResponse, JSONResponse as JSONr, HTMLResponse as HTMLr, Response, RedirectResponse
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 PrivateKey, PublicKey, load_file, Cert, ProductMapping
# Load variables
load_dotenv('../version.env')
@ -50,6 +50,9 @@ 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)
@ -248,6 +251,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": [],
@ -298,14 +302,19 @@ 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)
@ -331,7 +340,7 @@ 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)
@ -362,8 +371,8 @@ 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)
@ -396,22 +405,266 @@ 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)
# 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)
logger.debug(f'CALLED /leasing/v1/config-token')
logger.debug(f'Headers: {request.headers}')
logger.debug(f'Request: {j}')
# todo: THIS IS A DEMO ONLY
###
#
# https://git.collinwebdesigns.de/nvidia/nls/-/blob/main/src/test/test_config_token.py
#
###
root_private_key_filename = join(dirname(__file__), 'cert/my_demo_root_private_key.pem')
root_certificate_filename = join(dirname(__file__), 'cert/my_demo_root_certificate.pem')
ca_private_key_filename = join(dirname(__file__), 'cert/my_demo_ca_private_key.pem')
ca_certificate_filename = join(dirname(__file__), 'cert/my_demo_ca_certificate.pem')
si_private_key_filename = join(dirname(__file__), 'cert/my_demo_si_private_key.pem')
si_certificate_filename = join(dirname(__file__), 'cert/my_demo_si_certificate.pem')
def init_config_token_demo():
from cryptography import x509
from cryptography.hazmat._oid import NameOID
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key
from cryptography.hazmat.primitives.serialization import Encoding
""" 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.SubjectKeyIdentifier.from_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(root_private_key_filename, 'wb') as f:
f.write(my_root_private_key_as_pem)
with open(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_public_key(my_root_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(ca_private_key_filename, 'wb') as f:
f.write(my_ca_private_key_as_pem)
with open(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(si_private_key_filename, 'wb') as f:
f.write(my_si_private_key_as_pem)
# with open('instance.public.pem', '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, j.get('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(j.get('service_instance_ref'))
]), critical=False)
.sign(my_ca_private_key, hashes.SHA256()))
my_si_public_key_exp = my_si_certificate.public_key().public_numbers().e
my_si_public_key_mod = f'{my_si_certificate.public_key().public_numbers().n:x}' # hex value without "0x" prefix
with open(si_certificate_filename, 'wb') as f:
f.write(my_si_certificate.public_bytes(encoding=Encoding.PEM))
if not (isfile(root_private_key_filename)
and isfile(ca_private_key_filename)
and isfile(ca_certificate_filename)
and isfile(si_private_key_filename)
and isfile(si_certificate_filename)):
init_config_token_demo()
my_ca_certificate = Cert.from_file(ca_certificate_filename)
my_si_certificate = Cert.from_file(si_certificate_filename)
my_si_private_key = PrivateKey.from_file(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().raw()
my_si_public_key_as_pem = my_si_private_key.public_key().pem()
""" build out payload """
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": hex(my_si_public_key.public_numbers().n)[2:],
"exp": int(my_si_public_key.public_numbers().e),
},
# 64 chars per line (pem default)
"service_instance_public_key_pem": my_si_public_key_as_pem.decode('utf-8').strip(),
"key_retention_mode": "LATEST_ONLY"
},
}
my_jwt_encode_key = jwk.construct(my_si_private_key_as_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')
response_si_certificate = my_si_certificate.pem().decode('utf-8')
response = {
"certificateConfiguration": {
# 76 chars per line
"caChain": [response_ca_chain],
# 76 chars per line
"publicCert": response_si_certificate,
"publicKey": {
"exp": int(my_si_certificate.raw().public_key().public_numbers().e),
"mod": [hex(my_si_certificate.raw().public_key().public_numbers().n)[2:]],
},
},
"configToken": config_token,
}
logging.debug(response)
return JSONr(response, status_code=200)
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
@app.post('/leasing/v1/lessor', description='request multiple leases (borrow) for current origin')
async def leasing_v1_lessor(request: Request):
@ -424,39 +677,55 @@ async def leasing_v1_lessor(request: Request):
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}"]')
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)
logger.debug(response)
signature = INSTANCE_KEY_RSA.generate_signature(json_dumps(response, ensure_ascii=False, allow_nan=False, indent=None, separators=(",", ":")).encode('utf-8'))
signature = f'{signature.hex().encode()}'
return JSONr(response, headers={'access-control-expose-headers': 'X-NLS-Signature', 'X-NLS-Signature': signature})
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_multi_controller.py
@ -472,8 +741,8 @@ 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)
@ -483,7 +752,7 @@ async def leasing_v1_lessor_lease(request: Request):
# 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}')
@ -494,17 +763,22 @@ async def leasing_v1_lease_renew(request: Request, lease_ref: str):
expires = cur_time + LEASE_EXPIRE_DELTA
response = {
"client_challenge": j.get('client_challenge'),
"expires": expires.strftime(DT_FORMAT),
"feature_expired": False,
"lease_ref": lease_ref,
"expires": expires.isoformat(),
"recommended_lease_renewal": LEASE_RENEWAL_PERIOD,
"metadata": None,
"offline_lease": True,
"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)
signature = INSTANCE_KEY_RSA.generate_signature(json_dumps(response, ensure_ascii=False, allow_nan=False, indent=None, separators=(",", ":")).encode('utf-8'))
signature = f'{signature.hex().encode()}'
return JSONr(response, headers={'access-control-expose-headers': 'X-NLS-Signature', 'X-NLS-Signature': signature})
# venv/lib/python3.9/site-packages/nls_services_lease/test/test_lease_single_controller.py
@ -525,9 +799,10 @@ async def leasing_v1_lease_delete(request: Request, lease_ref: str):
return JSONr(status_code=404, content={'status': 404, 'detail': 'lease not found'})
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)
@ -547,8 +822,8 @@ 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)
@ -569,8 +844,8 @@ 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)

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,8 +1,11 @@
import logging
from json import loads as json_loads
from cryptography.hazmat.primitives import serialization
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 load_pem_private_key, load_pem_public_key
from cryptography.x509 import load_pem_x509_certificate, Certificate
logging.basicConfig()
@ -39,6 +42,9 @@ class PrivateKey:
)
return PublicKey(data=data)
def generate_signature(self, data: bytes) -> bytes:
return self.__key.sign(data, padding=PKCS1v15(), algorithm=SHA256())
@staticmethod
def generate(public_exponent: int = 65537, key_size: int = 2048) -> "PrivateKey":
log = logging.getLogger(__name__)
@ -76,6 +82,35 @@ class PublicKey:
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
def verify_signature(self, signature: bytes, data: bytes) -> bytes:
return self.__key.verify(signature, data, padding=PKCS1v15(), algorithm=SHA256())
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 signature(self) -> bytes:
return self.__cert.signature
def load_file(filename: str) -> bytes:
log = logging.getLogger(f'{__name__}')
log.debug(f'Loading contents of file "{filename}')
@ -126,3 +161,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

@ -1,3 +1,4 @@
import json
import sys
from base64 import b64encode as b64enc
from calendar import timegm
@ -7,7 +8,7 @@ from os.path import dirname, join
from uuid import uuid4, UUID
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
@ -20,6 +21,7 @@ from util import PrivateKey, PublicKey
client = TestClient(main.app)
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()
@ -38,6 +40,23 @@ def __bearer_token(origin_ref: str) -> str:
return token
def test_signing():
signature_set_header = INSTANCE_KEY_RSA.generate_signature(b'Hello')
# test plain
INSTANCE_KEY_PUB.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'))
INSTANCE_KEY_PUB.verify_signature(signature_get_header, b'Hello')
def test_index():
response = client.get('/')
assert response.status_code == 200
@ -69,6 +88,31 @@ def test_client_token():
assert response.status_code == 200
def test_config_token(): # todo: /leasing/v1/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_response_public_cert = nv_response_certificate_configuration.get('publicCert').encode('utf-8')
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 nv_si_public_key_me.get('mod') == 1 #nv_si_public_key_mod
assert len(nv_si_public_key_me.get('mod')) == 512
assert nv_si_public_key_me.get('exp') == 65537 # nv_si_public_key_exp
def test_origins():
pass
@ -168,12 +212,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 +227,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
INSTANCE_KEY_PUB.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 +261,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
INSTANCE_KEY_PUB.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 +301,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]